diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-12-13 17:22:17 +0100 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-12-13 17:22:17 +0100 |
commit | dc7a2bc3a9c8316e17902493b832ca117805d29f (patch) | |
tree | 3c1a03edf586869119ef0de03631f6f603650720 | |
parent | 7500a80fc06c5311c46df8f1761f25ae67277fc4 (diff) | |
download | lanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.tar.gz lanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.tar.bz2 lanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.zip |
Append all unit tests to depot
29 files changed, 4010 insertions, 0 deletions
diff --git a/unit_tests/UnitTests.vcxproj b/unit_tests/UnitTests.vcxproj new file mode 100644 index 0000000..adb90b5 --- /dev/null +++ b/unit_tests/UnitTests.vcxproj | |||
@@ -0,0 +1,354 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
3 | <ItemGroup Label="ProjectConfigurations"> | ||
4 | <ProjectConfiguration Include="Debug 5.1|Win32"> | ||
5 | <Configuration>Debug 5.1</Configuration> | ||
6 | <Platform>Win32</Platform> | ||
7 | </ProjectConfiguration> | ||
8 | <ProjectConfiguration Include="Debug 5.1|x64"> | ||
9 | <Configuration>Debug 5.1</Configuration> | ||
10 | <Platform>x64</Platform> | ||
11 | </ProjectConfiguration> | ||
12 | <ProjectConfiguration Include="Debug 5.2|Win32"> | ||
13 | <Configuration>Debug 5.2</Configuration> | ||
14 | <Platform>Win32</Platform> | ||
15 | </ProjectConfiguration> | ||
16 | <ProjectConfiguration Include="Debug 5.2|x64"> | ||
17 | <Configuration>Debug 5.2</Configuration> | ||
18 | <Platform>x64</Platform> | ||
19 | </ProjectConfiguration> | ||
20 | <ProjectConfiguration Include="Debug 5.3|Win32"> | ||
21 | <Configuration>Debug 5.3</Configuration> | ||
22 | <Platform>Win32</Platform> | ||
23 | </ProjectConfiguration> | ||
24 | <ProjectConfiguration Include="Debug 5.3|x64"> | ||
25 | <Configuration>Debug 5.3</Configuration> | ||
26 | <Platform>x64</Platform> | ||
27 | </ProjectConfiguration> | ||
28 | <ProjectConfiguration Include="Debug 5.4|Win32"> | ||
29 | <Configuration>Debug 5.4</Configuration> | ||
30 | <Platform>Win32</Platform> | ||
31 | </ProjectConfiguration> | ||
32 | <ProjectConfiguration Include="Release 5.4|Win32"> | ||
33 | <Configuration>Release 5.4</Configuration> | ||
34 | <Platform>Win32</Platform> | ||
35 | </ProjectConfiguration> | ||
36 | <ProjectConfiguration Include="Debug 5.4|x64"> | ||
37 | <Configuration>Debug 5.4</Configuration> | ||
38 | <Platform>x64</Platform> | ||
39 | </ProjectConfiguration> | ||
40 | <ProjectConfiguration Include="Release 5.4|x64"> | ||
41 | <Configuration>Release 5.4</Configuration> | ||
42 | <Platform>x64</Platform> | ||
43 | </ProjectConfiguration> | ||
44 | </ItemGroup> | ||
45 | <PropertyGroup Label="Globals"> | ||
46 | <ProjectGuid>{aed7f42f-139a-46ba-80fe-16e062ea1345}</ProjectGuid> | ||
47 | <Keyword>Win32Proj</Keyword> | ||
48 | <WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion> | ||
49 | <ConfigurationType>Application</ConfigurationType> | ||
50 | <PlatformToolset>v143</PlatformToolset> | ||
51 | <CharacterSet>Unicode</CharacterSet> | ||
52 | </PropertyGroup> | ||
53 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> | ||
54 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||
55 | <ImportGroup Label="ExtensionSettings" /> | ||
56 | <ImportGroup Label="Shared" /> | ||
57 | <ImportGroup Label="PropertySheets" /> | ||
58 | <PropertyGroup Label="UserMacros" /> | ||
59 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|Win32'"> | ||
60 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> | ||
61 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
62 | </PropertyGroup> | ||
63 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|Win32'"> | ||
64 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> | ||
65 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
66 | </PropertyGroup> | ||
67 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|Win32'"> | ||
68 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> | ||
69 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
70 | </PropertyGroup> | ||
71 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|Win32'"> | ||
72 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> | ||
73 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
74 | </PropertyGroup> | ||
75 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.4|Win32'"> | ||
76 | <OutDir>$(SolutionDir)_Output\$(ProjectName)\$(PlatformName)\$(Configuration)\</OutDir> | ||
77 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
78 | </PropertyGroup> | ||
79 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|x64'"> | ||
80 | <OutDir>E:\Boulot\anubis\Lua\framework\</OutDir> | ||
81 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
82 | </PropertyGroup> | ||
83 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|x64'"> | ||
84 | <OutDir>$(SolutionDir)..\Lua53\bin\$(Platform)\Debug\</OutDir> | ||
85 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
86 | </PropertyGroup> | ||
87 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|x64'"> | ||
88 | <OutDir>$(SolutionDir)..\Lua52\bin\$(Platform)\Debug\</OutDir> | ||
89 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
90 | </PropertyGroup> | ||
91 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|x64'"> | ||
92 | <OutDir>$(SolutionDir)..\Lua51\bin\$(Platform)\Debug\</OutDir> | ||
93 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
94 | </PropertyGroup> | ||
95 | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.4|x64'"> | ||
96 | <OutDir>E:\Boulot\anubis\Lua\framework\</OutDir> | ||
97 | <IntDir>$(SolutionDir)_Tmp\$(ProjectName)\$(PlatformName)\$(Configuration)\</IntDir> | ||
98 | </PropertyGroup> | ||
99 | <ItemDefinitionGroup /> | ||
100 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|Win32'"> | ||
101 | <ClCompile> | ||
102 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
103 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
104 | <Optimization>Disabled</Optimization> | ||
105 | <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
106 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
107 | <WarningLevel>Level3</WarningLevel> | ||
108 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua54\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
109 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
110 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
111 | </ClCompile> | ||
112 | <Link> | ||
113 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
114 | <SubSystem>Console</SubSystem> | ||
115 | <AdditionalDependencies>lua54.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
116 | </Link> | ||
117 | </ItemDefinitionGroup> | ||
118 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|Win32'"> | ||
119 | <ClCompile> | ||
120 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
121 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
122 | <Optimization>Disabled</Optimization> | ||
123 | <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
124 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
125 | <WarningLevel>Level3</WarningLevel> | ||
126 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua54\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
127 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
128 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
129 | </ClCompile> | ||
130 | <Link> | ||
131 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
132 | <SubSystem>Console</SubSystem> | ||
133 | <AdditionalDependencies>lua54.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
134 | </Link> | ||
135 | </ItemDefinitionGroup> | ||
136 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|Win32'"> | ||
137 | <ClCompile> | ||
138 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
139 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
140 | <Optimization>Disabled</Optimization> | ||
141 | <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
142 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
143 | <WarningLevel>Level3</WarningLevel> | ||
144 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua52\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
145 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
146 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
147 | </ClCompile> | ||
148 | <Link> | ||
149 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
150 | <SubSystem>Console</SubSystem> | ||
151 | <AdditionalDependencies>lua52.lib;gtestd.lib;gtest_maind.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
152 | </Link> | ||
153 | </ItemDefinitionGroup> | ||
154 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|Win32'"> | ||
155 | <ClCompile> | ||
156 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
157 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
158 | <Optimization>Disabled</Optimization> | ||
159 | <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
160 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
161 | <WarningLevel>Level3</WarningLevel> | ||
162 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua51\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
163 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
164 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
165 | </ClCompile> | ||
166 | <Link> | ||
167 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
168 | <SubSystem>Console</SubSystem> | ||
169 | <AdditionalDependencies>lua51.lib;gtestd.lib;gtest_maind.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
170 | </Link> | ||
171 | </ItemDefinitionGroup> | ||
172 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|x64'"> | ||
173 | <ClCompile> | ||
174 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
175 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
176 | <Optimization>Disabled</Optimization> | ||
177 | <PreprocessorDefinitions>X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
178 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
179 | <WarningLevel>Level3</WarningLevel> | ||
180 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua54\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
181 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
182 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
183 | </ClCompile> | ||
184 | <Link> | ||
185 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
186 | <SubSystem>Console</SubSystem> | ||
187 | <AdditionalLibraryDirectories>$(SolutionDir)..\Lua54\bin\$(Platform)\Debug;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-dyn\x64\Debug</AdditionalLibraryDirectories> | ||
188 | <AdditionalDependencies>lua54.lib;gtestd.lib;gtest_maind.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
189 | <IgnoreSpecificDefaultLibraries> | ||
190 | </IgnoreSpecificDefaultLibraries> | ||
191 | </Link> | ||
192 | </ItemDefinitionGroup> | ||
193 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|x64'"> | ||
194 | <ClCompile> | ||
195 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
196 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
197 | <Optimization>Disabled</Optimization> | ||
198 | <PreprocessorDefinitions>X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
199 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
200 | <WarningLevel>Level3</WarningLevel> | ||
201 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua53\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
202 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
203 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
204 | </ClCompile> | ||
205 | <Link> | ||
206 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
207 | <SubSystem>Console</SubSystem> | ||
208 | <AdditionalLibraryDirectories>$(SolutionDir)..\Lua53\bin\$(Platform)\Debug;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-dyn\x64\Debug</AdditionalLibraryDirectories> | ||
209 | <AdditionalDependencies>lua53.lib;gtestd.lib;gtest_maind.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
210 | <IgnoreSpecificDefaultLibraries> | ||
211 | </IgnoreSpecificDefaultLibraries> | ||
212 | </Link> | ||
213 | </ItemDefinitionGroup> | ||
214 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|x64'"> | ||
215 | <ClCompile> | ||
216 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
217 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
218 | <Optimization>Disabled</Optimization> | ||
219 | <PreprocessorDefinitions>X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
220 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
221 | <WarningLevel>Level3</WarningLevel> | ||
222 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua52\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
223 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
224 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
225 | </ClCompile> | ||
226 | <Link> | ||
227 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
228 | <SubSystem>Console</SubSystem> | ||
229 | <AdditionalLibraryDirectories>$(SolutionDir)..\Lua52\bin\$(Platform)\Debug;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-dyn\x64\Debug</AdditionalLibraryDirectories> | ||
230 | <AdditionalDependencies>lua52.lib;gtestd.lib;gtest_maind.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
231 | <IgnoreSpecificDefaultLibraries> | ||
232 | </IgnoreSpecificDefaultLibraries> | ||
233 | </Link> | ||
234 | </ItemDefinitionGroup> | ||
235 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|x64'"> | ||
236 | <ClCompile> | ||
237 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
238 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
239 | <Optimization>Disabled</Optimization> | ||
240 | <PreprocessorDefinitions>X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
241 | <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
242 | <WarningLevel>Level3</WarningLevel> | ||
243 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua51\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
244 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
245 | <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> | ||
246 | </ClCompile> | ||
247 | <Link> | ||
248 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
249 | <SubSystem>Console</SubSystem> | ||
250 | <AdditionalLibraryDirectories>$(SolutionDir)..\Lua51\bin\$(Platform)\Debug;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-dyn\x64\Debug</AdditionalLibraryDirectories> | ||
251 | <AdditionalDependencies>lua51.lib;gtestd.lib;gtest_maind.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
252 | <IgnoreSpecificDefaultLibraries> | ||
253 | </IgnoreSpecificDefaultLibraries> | ||
254 | </Link> | ||
255 | </ItemDefinitionGroup> | ||
256 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.4|Win32'"> | ||
257 | <ClCompile> | ||
258 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
259 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
260 | <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
261 | <WarningLevel>Level3</WarningLevel> | ||
262 | <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> | ||
263 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
264 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua54\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
265 | </ClCompile> | ||
266 | <Link> | ||
267 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
268 | <SubSystem>Console</SubSystem> | ||
269 | <OptimizeReferences>true</OptimizeReferences> | ||
270 | <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||
271 | <AdditionalDependencies>lua54.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
272 | </Link> | ||
273 | </ItemDefinitionGroup> | ||
274 | <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release 5.4|x64'"> | ||
275 | <ClCompile> | ||
276 | <PrecompiledHeader>Use</PrecompiledHeader> | ||
277 | <PrecompiledHeaderFile>_pch.hpp</PrecompiledHeaderFile> | ||
278 | <PreprocessorDefinitions>X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
279 | <WarningLevel>Level3</WarningLevel> | ||
280 | <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> | ||
281 | <LanguageStandard>stdcpp20</LanguageStandard> | ||
282 | <AdditionalIncludeDirectories>$(SolutionDir)..\Lua54\include;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\build\native\include\;$(SolutionDir)Lanes</AdditionalIncludeDirectories> | ||
283 | </ClCompile> | ||
284 | <Link> | ||
285 | <GenerateDebugInformation>true</GenerateDebugInformation> | ||
286 | <SubSystem>Console</SubSystem> | ||
287 | <OptimizeReferences>true</OptimizeReferences> | ||
288 | <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||
289 | <AdditionalLibraryDirectories>$(SolutionDir)..\Lua54\bin\$(Platform)\Release;$(SolutionDir)..\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-dyn.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-dyn\x64\Release</AdditionalLibraryDirectories> | ||
290 | <AdditionalDependencies>lua54.lib;gtest.lib;gtest_main.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies> | ||
291 | </Link> | ||
292 | </ItemDefinitionGroup> | ||
293 | <ItemGroup> | ||
294 | <ClInclude Include="..\src\compat.hpp" /> | ||
295 | <ClInclude Include="..\src\deep.hpp" /> | ||
296 | <ClInclude Include="_pch.hpp" /> | ||
297 | <ClInclude Include="shared.h" /> | ||
298 | </ItemGroup> | ||
299 | <ItemGroup> | ||
300 | <ClCompile Include="..\src\compat.cpp"> | ||
301 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|Win32'">NotUsing</PrecompiledHeader> | ||
302 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|Win32'">NotUsing</PrecompiledHeader> | ||
303 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|Win32'">NotUsing</PrecompiledHeader> | ||
304 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release 5.4|Win32'">NotUsing</PrecompiledHeader> | ||
305 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|Win32'">NotUsing</PrecompiledHeader> | ||
306 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|x64'">NotUsing</PrecompiledHeader> | ||
307 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|x64'">NotUsing</PrecompiledHeader> | ||
308 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|x64'">NotUsing</PrecompiledHeader> | ||
309 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release 5.4|x64'">NotUsing</PrecompiledHeader> | ||
310 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|x64'">NotUsing</PrecompiledHeader> | ||
311 | </ClCompile> | ||
312 | <ClCompile Include="..\src\deep.cpp" /> | ||
313 | <ClCompile Include="deep_tests.cpp" /> | ||
314 | <ClCompile Include="embedded_tests.cpp" /> | ||
315 | <ClCompile Include="lane_tests.cpp" /> | ||
316 | <ClCompile Include="legacy_tests.cpp" /> | ||
317 | <ClCompile Include="linda_tests.cpp" /> | ||
318 | <ClCompile Include="shared.cpp" /> | ||
319 | <ClCompile Include="init_and_shutdown.cpp" /> | ||
320 | <ClCompile Include="_pch.cpp"> | ||
321 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|Win32'">Create</PrecompiledHeader> | ||
322 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|Win32'">Create</PrecompiledHeader> | ||
323 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|Win32'">Create</PrecompiledHeader> | ||
324 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|Win32'">Create</PrecompiledHeader> | ||
325 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release 5.4|Win32'">Create</PrecompiledHeader> | ||
326 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.4|x64'">Create</PrecompiledHeader> | ||
327 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.3|x64'">Create</PrecompiledHeader> | ||
328 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.2|x64'">Create</PrecompiledHeader> | ||
329 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug 5.1|x64'">Create</PrecompiledHeader> | ||
330 | <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release 5.4|x64'">Create</PrecompiledHeader> | ||
331 | </ClCompile> | ||
332 | </ItemGroup> | ||
333 | <ItemGroup> | ||
334 | <None Include="scripts\coro\basics.lua" /> | ||
335 | <None Include="scripts\coro\error_handling.lua" /> | ||
336 | <None Include="scripts\lane\cooperative_shutdown.lua" /> | ||
337 | <None Include="scripts\lane\stdlib_naming.lua" /> | ||
338 | <None Include="scripts\lane\tasking_basic.lua" /> | ||
339 | <None Include="scripts\lane\tasking_cancelling.lua" /> | ||
340 | <None Include="scripts\lane\tasking_comms_criss_cross.lua" /> | ||
341 | <None Include="scripts\lane\tasking_communications.lua" /> | ||
342 | <None Include="scripts\lane\tasking_error.lua" /> | ||
343 | <None Include="scripts\lane\tasking_join_test.lua" /> | ||
344 | <None Include="scripts\lane\tasking_send_receive_code.lua" /> | ||
345 | <None Include="scripts\lane\uncooperative_shutdown.lua" /> | ||
346 | <None Include="scripts\linda\multiple_keepers.lua" /> | ||
347 | <None Include="scripts\linda\send_receive.lua" /> | ||
348 | <None Include="scripts\linda\send_registered_userdata.lua" /> | ||
349 | <None Include="scripts\_assert.lua" /> | ||
350 | <None Include="scripts\_utils.lua" /> | ||
351 | </ItemGroup> | ||
352 | <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||
353 | <ImportGroup Label="ExtensionTargets" /> | ||
354 | </Project> \ No newline at end of file | ||
diff --git a/unit_tests/UnitTests.vcxproj.filters b/unit_tests/UnitTests.vcxproj.filters new file mode 100644 index 0000000..64c2a73 --- /dev/null +++ b/unit_tests/UnitTests.vcxproj.filters | |||
@@ -0,0 +1,99 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
3 | <ItemGroup> | ||
4 | <ClCompile Include="legacy_tests.cpp" /> | ||
5 | <ClCompile Include="linda_tests.cpp" /> | ||
6 | <ClCompile Include="shared.cpp" /> | ||
7 | <ClCompile Include="init_and_shutdown.cpp" /> | ||
8 | <ClCompile Include="_pch.cpp" /> | ||
9 | <ClCompile Include="lane_tests.cpp" /> | ||
10 | <ClCompile Include="..\src\compat.cpp"> | ||
11 | <Filter>Lanes</Filter> | ||
12 | </ClCompile> | ||
13 | <ClCompile Include="embedded_tests.cpp" /> | ||
14 | <ClCompile Include="deep_tests.cpp" /> | ||
15 | <ClCompile Include="..\src\deep.cpp"> | ||
16 | <Filter>Lanes</Filter> | ||
17 | </ClCompile> | ||
18 | </ItemGroup> | ||
19 | <ItemGroup> | ||
20 | <ClInclude Include="_pch.hpp" /> | ||
21 | <ClInclude Include="shared.h" /> | ||
22 | <ClInclude Include="..\src\deep.hpp"> | ||
23 | <Filter>Lanes</Filter> | ||
24 | </ClInclude> | ||
25 | <ClInclude Include="..\src\compat.hpp"> | ||
26 | <Filter>Lanes</Filter> | ||
27 | </ClInclude> | ||
28 | </ItemGroup> | ||
29 | <ItemGroup> | ||
30 | <Filter Include="Scripts"> | ||
31 | <UniqueIdentifier>{06101607-f449-40ce-970a-55fc8387aa97}</UniqueIdentifier> | ||
32 | </Filter> | ||
33 | <Filter Include="Scripts\linda"> | ||
34 | <UniqueIdentifier>{309f0ee2-05fe-42f8-9011-7d896e58b1d3}</UniqueIdentifier> | ||
35 | </Filter> | ||
36 | <Filter Include="Lanes"> | ||
37 | <UniqueIdentifier>{dc01ee0b-82d1-4a12-9bcd-d9caee1b064c}</UniqueIdentifier> | ||
38 | </Filter> | ||
39 | <Filter Include="Scripts\lane"> | ||
40 | <UniqueIdentifier>{fb19e619-8de3-4d82-84d0-21e80d63331d}</UniqueIdentifier> | ||
41 | </Filter> | ||
42 | <Filter Include="Scripts\coro"> | ||
43 | <UniqueIdentifier>{3cf4f618-1863-4de0-8761-5fca21e6b07c}</UniqueIdentifier> | ||
44 | </Filter> | ||
45 | </ItemGroup> | ||
46 | <ItemGroup> | ||
47 | <None Include="scripts\linda\send_receive.lua"> | ||
48 | <Filter>Scripts\linda</Filter> | ||
49 | </None> | ||
50 | <None Include="scripts\linda\multiple_keepers.lua"> | ||
51 | <Filter>Scripts\linda</Filter> | ||
52 | </None> | ||
53 | <None Include="scripts\lane\cooperative_shutdown.lua"> | ||
54 | <Filter>Scripts\lane</Filter> | ||
55 | </None> | ||
56 | <None Include="scripts\lane\uncooperative_shutdown.lua"> | ||
57 | <Filter>Scripts\lane</Filter> | ||
58 | </None> | ||
59 | <None Include="scripts\lane\tasking_basic.lua"> | ||
60 | <Filter>Scripts\lane</Filter> | ||
61 | </None> | ||
62 | <None Include="scripts\_assert.lua"> | ||
63 | <Filter>Scripts</Filter> | ||
64 | </None> | ||
65 | <None Include="scripts\_utils.lua"> | ||
66 | <Filter>Scripts</Filter> | ||
67 | </None> | ||
68 | <None Include="scripts\lane\tasking_cancelling.lua"> | ||
69 | <Filter>Scripts\lane</Filter> | ||
70 | </None> | ||
71 | <None Include="scripts\lane\tasking_communications.lua"> | ||
72 | <Filter>Scripts\lane</Filter> | ||
73 | </None> | ||
74 | <None Include="scripts\lane\stdlib_naming.lua"> | ||
75 | <Filter>Scripts\lane</Filter> | ||
76 | </None> | ||
77 | <None Include="scripts\lane\tasking_comms_criss_cross.lua"> | ||
78 | <Filter>Scripts\lane</Filter> | ||
79 | </None> | ||
80 | <None Include="scripts\lane\tasking_send_receive_code.lua"> | ||
81 | <Filter>Scripts\lane</Filter> | ||
82 | </None> | ||
83 | <None Include="scripts\lane\tasking_join_test.lua"> | ||
84 | <Filter>Scripts\lane</Filter> | ||
85 | </None> | ||
86 | <None Include="scripts\coro\basics.lua"> | ||
87 | <Filter>Scripts\coro</Filter> | ||
88 | </None> | ||
89 | <None Include="scripts\coro\error_handling.lua"> | ||
90 | <Filter>Scripts\coro</Filter> | ||
91 | </None> | ||
92 | <None Include="scripts\lane\tasking_error.lua"> | ||
93 | <Filter>Scripts\lane</Filter> | ||
94 | </None> | ||
95 | <None Include="scripts\linda\send_registered_userdata.lua"> | ||
96 | <Filter>Scripts\linda</Filter> | ||
97 | </None> | ||
98 | </ItemGroup> | ||
99 | </Project> \ No newline at end of file | ||
diff --git a/unit_tests/_pch.cpp b/unit_tests/_pch.cpp new file mode 100644 index 0000000..38b27e8 --- /dev/null +++ b/unit_tests/_pch.cpp | |||
@@ -0,0 +1,4 @@ | |||
1 | #include "_pch.hpp" | ||
2 | |||
3 | // IMPORTANT INFORMATION: some relative paths coded in the test implementations suppose that the cwd when debugging is $(SolutionDir)Lanes | ||
4 | // Therefore that's what needs to be set in Google Test Adapter 'Working Directory' global setting... \ No newline at end of file | ||
diff --git a/unit_tests/_pch.hpp b/unit_tests/_pch.hpp new file mode 100644 index 0000000..5d3c3e9 --- /dev/null +++ b/unit_tests/_pch.hpp | |||
@@ -0,0 +1,23 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include <filesystem> | ||
4 | #include <source_location> | ||
5 | #include <mutex> | ||
6 | #include <variant> | ||
7 | |||
8 | #include "gtest/gtest.h" | ||
9 | |||
10 | #ifdef __cplusplus | ||
11 | extern "C" | ||
12 | { | ||
13 | #endif // __cplusplus | ||
14 | #include "lua.h" | ||
15 | #include "lualib.h" | ||
16 | #include "lauxlib.h" | ||
17 | #ifdef __cplusplus | ||
18 | } | ||
19 | #endif // __cplusplus | ||
20 | |||
21 | #include "lanes/src/allocator.hpp" | ||
22 | #include "lanes/src/compat.hpp" | ||
23 | #include "lanes/src/universe.hpp" | ||
diff --git a/unit_tests/deep_tests.cpp b/unit_tests/deep_tests.cpp new file mode 100644 index 0000000..c1708e1 --- /dev/null +++ b/unit_tests/deep_tests.cpp | |||
@@ -0,0 +1,101 @@ | |||
1 | #include "_pch.hpp" | ||
2 | #include "shared.h" | ||
3 | |||
4 | // yeah it's dirty, I will do better someday | ||
5 | #include "../deep_test/deep_test.cpp" | ||
6 | |||
7 | |||
8 | // ################################################################################################# | ||
9 | // ################################################################################################# | ||
10 | |||
11 | class DeepTests : public ::testing::Test | ||
12 | { | ||
13 | protected: | ||
14 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
15 | |||
16 | void SetUp() override | ||
17 | { | ||
18 | std::ignore = S.doString("lanes = require 'lanes'.configure()"); | ||
19 | std::ignore = S.doString("fixture = require 'fixture'"); | ||
20 | std::ignore = S.doString("deep_test = require 'deep_test'"); | ||
21 | } | ||
22 | }; | ||
23 | |||
24 | // ################################################################################################# | ||
25 | // ################################################################################################# | ||
26 | |||
27 | TEST_F(DeepTests, DeepIsCollected) | ||
28 | { | ||
29 | ASSERT_EQ(S.doString("assert(true)"), LuaError::OK) << S; | ||
30 | ASSERT_NE(S.doString("assert(false)"), LuaError::OK) << S; | ||
31 | if constexpr (LUA_VERSION_NUM >= 503) { // Lua < 5.3 only supports a table uservalue | ||
32 | ASSERT_EQ(S.doString( | ||
33 | // create a deep userdata object without referencing it. First uservalue is a function, and should be called on __gc | ||
34 | " deep_test.new_deep(1):setuv(1, function() collected = collected and collected + 1 or 1 end)" | ||
35 | " deep_test.new_deep(1):setuv(1, function() collected = collected and collected + 1 or 1 end)" | ||
36 | " collectgarbage()" // and collect it | ||
37 | " assert(collected == 2)" | ||
38 | ), LuaError::OK) << S; | ||
39 | } | ||
40 | } | ||
41 | |||
42 | // ################################################################################################# | ||
43 | |||
44 | TEST_F(DeepTests, DeepRefcounting) | ||
45 | { | ||
46 | ASSERT_EQ(S.doString( | ||
47 | " d = deep_test.new_deep(1)" // create a deep userdata object | ||
48 | " d:set(42)" // set some value | ||
49 | " assert(d:refcount() == 1)" | ||
50 | ), LuaError::OK) << S; | ||
51 | ASSERT_EQ(S.doString( | ||
52 | " l = lanes.linda()" | ||
53 | " b, s = l:set('k', d, d)" // store it twice in the linda | ||
54 | " assert(b == false and s == 'under')" // no waking, under capacity | ||
55 | " assert(d:refcount() == 2)" // 1 ref here, 1 in the keeper (even if we sent it twice) | ||
56 | ), LuaError::OK) << S; | ||
57 | ASSERT_EQ(S.doString( | ||
58 | " n, d = l:get('k')" // pull it out of the linda | ||
59 | " assert(n == 1 and type(d) == 'userdata')" // got 1 item out of the linda | ||
60 | " assert(d:get() == 42 and d:refcount() == 2)" // 1 ref here, 1 in the keeper (even if we sent it twice) | ||
61 | ), LuaError::OK) << S; | ||
62 | ASSERT_EQ(S.doString( | ||
63 | " l = nil" | ||
64 | " collectgarbage()" // clears the linda, removes its storage from the keeper | ||
65 | " lanes.collectgarbage()" // collect garbage inside the keepers too, to finish cleanup | ||
66 | " assert(d:refcount() == 1)" // 1 ref here | ||
67 | ), LuaError::OK) << S; | ||
68 | if constexpr (LUA_VERSION_NUM >= 503) { // Lua < 5.3 only supports a table uservalue | ||
69 | ASSERT_EQ(S.doString( | ||
70 | " d:setuv(1, function() collected = collected and collected + 1 or 1 end)" | ||
71 | " d = nil" // clear last reference | ||
72 | " collectgarbage()" // force collection | ||
73 | " assert(collected == 1)" // we should see it | ||
74 | ), LuaError::OK) << S; | ||
75 | } | ||
76 | } | ||
77 | |||
78 | // ################################################################################################# | ||
79 | |||
80 | TEST_F(DeepTests, DeepCollectedFromInsideLinda) | ||
81 | { | ||
82 | ASSERT_EQ(S.doString( | ||
83 | " d = deep_test.new_deep(1)" // create a deep userdata object | ||
84 | " d:set(42)" // set some value | ||
85 | " assert(d:refcount() == 1)" | ||
86 | ), LuaError::OK) << S; | ||
87 | ASSERT_EQ(S.doString( | ||
88 | " l = lanes.linda()" | ||
89 | " b, s = l:set('k', d, d)" // store it twice in the linda | ||
90 | " assert(b == false and s == 'under')" // no waking, under capacity | ||
91 | " assert(d:refcount() == 2)" // 1 ref here, 1 in the keeper (even if we sent it twice) | ||
92 | ), LuaError::OK) << S; | ||
93 | ASSERT_EQ(S.doString( | ||
94 | " d = nil" | ||
95 | " collectgarbage()" // force collection | ||
96 | " l = nil" | ||
97 | " collectgarbage()" // clears the linda, removes its storage from the keeper | ||
98 | " lanes.collectgarbage()" // collect garbage inside the keepers too, to finish cleanup | ||
99 | " assert(deep_test.get_deep_count() == 0)" | ||
100 | ), LuaError::OK) << S; | ||
101 | } \ No newline at end of file | ||
diff --git a/unit_tests/embedded_tests.cpp b/unit_tests/embedded_tests.cpp new file mode 100644 index 0000000..03905d7 --- /dev/null +++ b/unit_tests/embedded_tests.cpp | |||
@@ -0,0 +1,140 @@ | |||
1 | #include "_pch.hpp" | ||
2 | #include "shared.h" | ||
3 | #include "lanes/src/lanes.hpp" | ||
4 | |||
5 | #include <windows.h> | ||
6 | |||
7 | // ################################################################################################# | ||
8 | |||
9 | namespace | ||
10 | { | ||
11 | namespace local | ||
12 | { | ||
13 | static int load_lanes_lua(lua_State* const L_) | ||
14 | { | ||
15 | if (0 == luaL_dofile(L_, "lanes.lua")) { | ||
16 | return 1; | ||
17 | } else { | ||
18 | return 0; | ||
19 | } | ||
20 | } | ||
21 | } // namespace local | ||
22 | } | ||
23 | |||
24 | // ################################################################################################# | ||
25 | |||
26 | class EmbeddedTests : public ::testing::Test | ||
27 | { | ||
28 | private: | ||
29 | HMODULE hCore{}; | ||
30 | |||
31 | protected: | ||
32 | LuaState S{ LuaState::WithBaseLibs{ false }, LuaState::WithFixture{ false } }; | ||
33 | lua_CFunction lanes_register{}; | ||
34 | |||
35 | void SetUp() override | ||
36 | { | ||
37 | hCore = LoadLibraryW(L"lanes\\core"); | ||
38 | if (!hCore) { | ||
39 | throw std::logic_error("Could not load lanes.core"); | ||
40 | } | ||
41 | luaopen_lanes_embedded_t const _p_luaopen_lanes_embedded{ reinterpret_cast<luaopen_lanes_embedded_t>(GetProcAddress(hCore, "luaopen_lanes_embedded")) }; | ||
42 | if (!_p_luaopen_lanes_embedded) { | ||
43 | throw std::logic_error("Could not bind luaopen_lanes_embedded"); | ||
44 | } | ||
45 | |||
46 | lanes_register = reinterpret_cast<lua_CFunction>(GetProcAddress(hCore, "lanes_register")); | ||
47 | if (!lanes_register) { | ||
48 | throw std::logic_error("Could not bind lanes_register"); | ||
49 | } | ||
50 | // need base to make lanes happy | ||
51 | luaL_requiref(S, LUA_GNAME, luaopen_base, 1); | ||
52 | lua_pop(S, 1); | ||
53 | S.stackCheck(0); | ||
54 | |||
55 | // need package to be able to require lanes | ||
56 | luaL_requiref(S, LUA_LOADLIBNAME, luaopen_package, 1); | ||
57 | lua_pop(S, 1); | ||
58 | S.stackCheck(0); | ||
59 | |||
60 | // need table to make lanes happy | ||
61 | luaL_requiref(S, LUA_TABLIBNAME, luaopen_table, 1); | ||
62 | lua_pop(S, 1); | ||
63 | S.stackCheck(0); | ||
64 | |||
65 | // need string to make lanes happy | ||
66 | luaL_requiref(S, LUA_STRLIBNAME, luaopen_string, 1); | ||
67 | lua_pop(S, 1); | ||
68 | S.stackCheck(0); | ||
69 | |||
70 | _p_luaopen_lanes_embedded(S, local::load_lanes_lua); // S: lanes | ||
71 | lua_pop(S, 1); | ||
72 | S.stackCheck(0); | ||
73 | } | ||
74 | |||
75 | void TearDown() override | ||
76 | { | ||
77 | // close state manually before we unload lanes.core | ||
78 | S.close(); | ||
79 | FreeLibrary(hCore); | ||
80 | } | ||
81 | }; | ||
82 | |||
83 | TEST_F(EmbeddedTests, SingleState) | ||
84 | { | ||
85 | // this sends data in a linda. current contents: | ||
86 | // key: short string | ||
87 | // values: | ||
88 | // bool | ||
89 | // integer | ||
90 | // number | ||
91 | // long string | ||
92 | // table with array and hash parts | ||
93 | // function with an upvalue | ||
94 | std::string_view const script{ | ||
95 | " local lanes = require 'lanes'.configure{with_timers = false}" | ||
96 | " local l = lanes.linda'gleh'" | ||
97 | " local upvalue = 'oeauaoeuoeuaoeuaoeujaoefubycfjbycfybcfjybcfjybcfjbcf'" | ||
98 | " local upvalued = function()" | ||
99 | " return upvalue" | ||
100 | " end" | ||
101 | " local t = setmetatable({ true, true, true, a = true}, {__index = rawget })" | ||
102 | " l:set('yo', true, 10, 100.0, upvalue, t, upvalued)" // put a breakpoint in linda_set to have some data to explore with the NATVIS | ||
103 | " return 'SUCCESS'" | ||
104 | }; | ||
105 | std::string_view result = S.doStringAndRet(script); | ||
106 | ASSERT_EQ(result, "SUCCESS"); | ||
107 | } | ||
108 | |||
109 | // ################################################################################################# | ||
110 | |||
111 | TEST_F(EmbeddedTests, ManualRegister) | ||
112 | { | ||
113 | ASSERT_EQ(S.doString("require 'lanes'.configure{with_timers = false}"), LuaError::OK) << S; | ||
114 | lua_pop(S, 1); | ||
115 | |||
116 | // require 'io' library after Lanes is initialized: | ||
117 | luaL_requiref(S, LUA_IOLIBNAME, luaopen_io, 1); | ||
118 | lua_pop(S, 1); | ||
119 | S.stackCheck(0); | ||
120 | |||
121 | // try to send io.open into a linda | ||
122 | std::string_view const script{ | ||
123 | "local lanes = require 'lanes'.configure{with_timers = false}" | ||
124 | "local l = lanes.linda'gleh'" | ||
125 | "l:set('yo', io.open)" | ||
126 | "return 'SUCCESS'" | ||
127 | }; | ||
128 | std::string_view const result1{ S.doStringAndRet(script) }; | ||
129 | ASSERT_NE(result1, "SUCCESS") << S; // S: "msg" | ||
130 | lua_pop(S, 1); // S: | ||
131 | |||
132 | // try again after manual registration | ||
133 | lua_pushcfunction(S, lanes_register); // S: lanes_register | ||
134 | luaG_pushstring(S, LUA_IOLIBNAME); // S: lanes_register "io" | ||
135 | luaL_requiref(S, LUA_IOLIBNAME, luaopen_io, 1); // S: lanes_register "io" io | ||
136 | lua_call(S, 2, 0); // S: | ||
137 | S.stackCheck(0); | ||
138 | std::string_view const result2{ S.doStringAndRet(script) }; | ||
139 | ASSERT_EQ(result2, "SUCCESS") << S; | ||
140 | } | ||
diff --git a/unit_tests/init_and_shutdown.cpp b/unit_tests/init_and_shutdown.cpp new file mode 100644 index 0000000..d45b52f --- /dev/null +++ b/unit_tests/init_and_shutdown.cpp | |||
@@ -0,0 +1,793 @@ | |||
1 | #include "_pch.hpp" | ||
2 | #include "shared.h" | ||
3 | |||
4 | // ################################################################################################# | ||
5 | |||
6 | TEST(Require, MissingBaseLibraries) | ||
7 | { | ||
8 | LuaState L{ LuaState::WithBaseLibs{ false }, LuaState::WithFixture{ false } }; | ||
9 | |||
10 | // no base library loaded means no print() | ||
11 | EXPECT_NE(L.doString("print('hello')"), LuaError::OK); | ||
12 | L.stackCheck(1); | ||
13 | lua_pop(L, 1); | ||
14 | |||
15 | // need require() to require lanes | ||
16 | luaL_requiref(L, LUA_LOADLIBNAME, luaopen_package, 0); | ||
17 | lua_pop(L, 1); | ||
18 | L.stackCheck(0); | ||
19 | |||
20 | // no base library loaded means lanes should issue an error | ||
21 | EXPECT_NE(L.doString("require 'lanes'"), LuaError::OK); | ||
22 | lua_pop(L, 1); | ||
23 | L.stackCheck(0); | ||
24 | |||
25 | // need base to make lanes happy | ||
26 | luaL_requiref(L, LUA_GNAME, luaopen_base, 1); | ||
27 | lua_pop(L, 1); | ||
28 | L.stackCheck(0); | ||
29 | |||
30 | // no table library loaded means lanes should issue an error | ||
31 | EXPECT_NE(L.doString("require 'lanes'"), LuaError::OK); | ||
32 | L.stackCheck(1); | ||
33 | lua_pop(L, 1); | ||
34 | |||
35 | // need table to make lanes happy | ||
36 | luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1); | ||
37 | lua_pop(L, 1); | ||
38 | L.stackCheck(0); | ||
39 | |||
40 | // no string library loaded means lanes should issue an error | ||
41 | EXPECT_NE(L.doString("require 'lanes'"), LuaError::OK); | ||
42 | lua_pop(L, 1); | ||
43 | L.stackCheck(0); | ||
44 | |||
45 | // need string to make lanes happy | ||
46 | luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1); | ||
47 | lua_pop(L, 1); | ||
48 | L.stackCheck(0); | ||
49 | |||
50 | // all required libraries are here: we should be happy | ||
51 | // that's only the case for Lua > 5.1 though, because the latter can't require() a module after a previously failed attempt (like we just did) | ||
52 | if constexpr (LUA_VERSION_NUM > 501) { | ||
53 | ASSERT_EQ(L.doString("require 'lanes'"), LuaError::OK); | ||
54 | } else { | ||
55 | // so let's do a fresh attempt in a virgin state where we have the 3 base libraries we need (plus 'package' to be able to require it of course) | ||
56 | LuaState L51{ LuaState::WithBaseLibs{ false }, LuaState::WithFixture{ false } }; | ||
57 | luaL_requiref(L51, LUA_LOADLIBNAME, luaopen_package, 1); | ||
58 | luaL_requiref(L51, LUA_GNAME, luaopen_base, 1); | ||
59 | luaL_requiref(L51, LUA_TABLIBNAME, luaopen_table, 1); | ||
60 | luaL_requiref(L51, LUA_STRLIBNAME, luaopen_string, 1); | ||
61 | lua_settop(L51, 0); | ||
62 | ASSERT_EQ(L51.doString("require 'lanes'"), LuaError::OK); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | class Configure : public ::testing::Test | ||
67 | { | ||
68 | protected: | ||
69 | LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
70 | }; | ||
71 | |||
72 | // ################################################################################################# | ||
73 | // ################################################################################################# | ||
74 | // allocator should be "protected", a C function returning a suitable userdata, or nil | ||
75 | |||
76 | TEST_F(Configure, AllocatorFalse) | ||
77 | { | ||
78 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = false}"), LuaError::OK); | ||
79 | } | ||
80 | |||
81 | // ################################################################################################# | ||
82 | |||
83 | TEST_F(Configure, AllocatorTrue) | ||
84 | { | ||
85 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = true}"), LuaError::OK); | ||
86 | } | ||
87 | |||
88 | // ################################################################################################# | ||
89 | |||
90 | TEST_F(Configure, AllocatorNumber) | ||
91 | { | ||
92 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = 33}"), LuaError::OK); | ||
93 | } | ||
94 | |||
95 | // ################################################################################################# | ||
96 | |||
97 | TEST_F(Configure, AllocatorTable) | ||
98 | { | ||
99 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = {}}"), LuaError::OK); | ||
100 | } | ||
101 | |||
102 | // ################################################################################################# | ||
103 | |||
104 | TEST_F(Configure, AllocatorLuaFunction) | ||
105 | { | ||
106 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = function() return {}, 12, 'yoy' end}"), LuaError::OK); | ||
107 | } | ||
108 | |||
109 | // ################################################################################################# | ||
110 | |||
111 | TEST_F(Configure, AllocatorBadCFunction) | ||
112 | { | ||
113 | // a C function that doesn't return what we expect should cause an error too | ||
114 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = print}"), LuaError::OK); | ||
115 | } | ||
116 | |||
117 | // ################################################################################################# | ||
118 | |||
119 | TEST_F(Configure, AllocatorTypo) | ||
120 | { | ||
121 | // oops, a typo | ||
122 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = 'Protected'}"), LuaError::OK); | ||
123 | } | ||
124 | |||
125 | // ################################################################################################# | ||
126 | |||
127 | TEST_F(Configure, AllocatorProtected) | ||
128 | { | ||
129 | // no typo, should work | ||
130 | EXPECT_EQ(L.doString("require 'lanes'.configure{allocator = 'protected'}"), LuaError::OK); | ||
131 | } | ||
132 | |||
133 | // ################################################################################################# | ||
134 | |||
135 | TEST_F(Configure, AllocatorCustomOk) | ||
136 | { | ||
137 | // a function that provides what we expect is fine | ||
138 | static constexpr lua_CFunction _provideAllocator = +[](lua_State* const L_) { | ||
139 | lanes::AllocatorDefinition* const _def{ new (L_) lanes::AllocatorDefinition{} }; | ||
140 | _def->initFrom(L_); | ||
141 | return 1; | ||
142 | }; | ||
143 | lua_pushcfunction(L, _provideAllocator); | ||
144 | lua_setglobal(L, "ProvideAllocator"); | ||
145 | EXPECT_EQ(L.doString("require 'lanes'.configure{allocator = ProvideAllocator}"), LuaError::OK); | ||
146 | } | ||
147 | |||
148 | // ################################################################################################# | ||
149 | |||
150 | TEST_F(Configure, AllocatorCustomWrongResultType) | ||
151 | { | ||
152 | // a function that provides something that is definitely not an AllocatorDefinition, should cause an error | ||
153 | static constexpr lua_CFunction _provideAllocator = +[](lua_State* const L_) { | ||
154 | lua_newtable(L_); | ||
155 | return 1; | ||
156 | }; | ||
157 | lua_pushcfunction(L, _provideAllocator); | ||
158 | lua_setglobal(L, "ProvideAllocator"); | ||
159 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = ProvideAllocator}"), LuaError::OK); | ||
160 | } | ||
161 | |||
162 | // ################################################################################################# | ||
163 | |||
164 | TEST_F(Configure, AllocatorCustomSignatureMismatch) | ||
165 | { | ||
166 | // a function that provides something that is too small to contain an AllocatorDefinition, should cause an error | ||
167 | static constexpr lua_CFunction _provideAllocator = +[](lua_State* const L_) { | ||
168 | // create a full userdata that is too small (it only contains enough to store a version tag, but not the rest | ||
169 | auto* const _duck{ static_cast<lanes::AllocatorDefinition::version_t*>(lua_newuserdata(L_, sizeof(lanes::AllocatorDefinition::version_t))) }; | ||
170 | *_duck = 666777; | ||
171 | return 1; | ||
172 | }; | ||
173 | lua_pushcfunction(L, _provideAllocator); | ||
174 | lua_setglobal(L, "ProvideAllocator"); | ||
175 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = ProvideAllocator}"), LuaError::OK); | ||
176 | } | ||
177 | |||
178 | // ################################################################################################# | ||
179 | |||
180 | TEST_F(Configure, AllocatorCustomSizeMismatch) | ||
181 | { | ||
182 | // a function that provides something that attempts to pass as an AllocatorDefinition, but is not, should cause an error | ||
183 | static constexpr lua_CFunction _provideAllocator = +[](lua_State* const L_) { | ||
184 | // create a full userdata of the correct size, but of course the contents don't match | ||
185 | int* const _duck{ static_cast<int*>(lua_newuserdata(L_, sizeof(lanes::AllocatorDefinition))) }; | ||
186 | _duck[0] = 666; | ||
187 | _duck[1] = 777; | ||
188 | return 1; | ||
189 | }; | ||
190 | lua_pushcfunction(L, _provideAllocator); | ||
191 | lua_setglobal(L, "ProvideAllocator"); | ||
192 | EXPECT_NE(L.doString("require 'lanes'.configure{allocator = ProvideAllocator}"), LuaError::OK); | ||
193 | } | ||
194 | |||
195 | // ################################################################################################# | ||
196 | // ################################################################################################# | ||
197 | // internal_allocator should be a string, "libc"/"allocator" | ||
198 | |||
199 | TEST_F(Configure, InternalAllocatorFalse) | ||
200 | { | ||
201 | EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = false}"), LuaError::OK); | ||
202 | } | ||
203 | |||
204 | // ################################################################################################# | ||
205 | |||
206 | TEST_F(Configure, InternalAllocatorTrue) | ||
207 | { | ||
208 | EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = true}"), LuaError::OK); | ||
209 | } | ||
210 | |||
211 | // ################################################################################################# | ||
212 | |||
213 | TEST_F(Configure, InternalAllocatorTable) | ||
214 | { | ||
215 | EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = {}}"), LuaError::OK); | ||
216 | } | ||
217 | |||
218 | // ################################################################################################# | ||
219 | |||
220 | TEST_F(Configure, InternalAllocatorFunction) | ||
221 | { | ||
222 | EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = function() end}"), LuaError::OK); | ||
223 | } | ||
224 | |||
225 | // ################################################################################################# | ||
226 | |||
227 | TEST_F(Configure, InternalAllocatorString) | ||
228 | { | ||
229 | EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = 'gluh'}"), LuaError::OK); | ||
230 | } | ||
231 | |||
232 | // ################################################################################################# | ||
233 | |||
234 | TEST_F(Configure, InternalAllocatorLibc) | ||
235 | { | ||
236 | EXPECT_EQ(L.doString("require 'lanes'.configure{internal_allocator = 'libc'}"), LuaError::OK); | ||
237 | } | ||
238 | |||
239 | // ################################################################################################# | ||
240 | |||
241 | TEST_F(Configure, InternalAllocatorAllocator) | ||
242 | { | ||
243 | EXPECT_EQ(L.doString("require 'lanes'.configure{internal_allocator = 'allocator'}"), LuaError::OK); | ||
244 | } | ||
245 | |||
246 | // ################################################################################################# | ||
247 | // ################################################################################################# | ||
248 | // keepers_gc_threshold should be a number in [0, 100] | ||
249 | |||
250 | TEST_F(Configure, KeepersGcThresholdTable) | ||
251 | { | ||
252 | EXPECT_NE(L.doString("require 'lanes'.configure{keepers_gc_threshold = {}}"), LuaError::OK); | ||
253 | } | ||
254 | |||
255 | // ################################################################################################# | ||
256 | |||
257 | TEST_F(Configure, KeepersGcThresholdString) | ||
258 | { | ||
259 | EXPECT_NE(L.doString("require 'lanes'.configure{keepers_gc_threshold = 'gluh'}"), LuaError::OK); | ||
260 | } | ||
261 | |||
262 | // ################################################################################################# | ||
263 | |||
264 | TEST_F(Configure, KeepersGcThresholdNegative) | ||
265 | { | ||
266 | EXPECT_EQ(L.doString("require 'lanes'.configure{keepers_gc_threshold = -1}"), LuaError::OK); | ||
267 | } | ||
268 | |||
269 | // ################################################################################################# | ||
270 | |||
271 | TEST_F(Configure, KeepersGcThresholdZero) | ||
272 | { | ||
273 | EXPECT_EQ(L.doString("require 'lanes'.configure{keepers_gc_threshold = 0}"), LuaError::OK); | ||
274 | } | ||
275 | |||
276 | // ################################################################################################# | ||
277 | |||
278 | TEST_F(Configure, KeepersGcThresholdHundred) | ||
279 | { | ||
280 | EXPECT_EQ(L.doString("require 'lanes'.configure{keepers_gc_threshold = 100}"), LuaError::OK); | ||
281 | } | ||
282 | |||
283 | // ################################################################################################# | ||
284 | // ################################################################################################# | ||
285 | // nb_user_keepers should be a number in [0, 100] | ||
286 | |||
287 | TEST_F(Configure, NbUserKeepersTable) | ||
288 | { | ||
289 | EXPECT_NE(L.doString("require 'lanes'.configure{nb_user_keepers = {}}"), LuaError::OK); | ||
290 | } | ||
291 | |||
292 | // ################################################################################################# | ||
293 | |||
294 | TEST_F(Configure, NbUserKeepersString) | ||
295 | { | ||
296 | EXPECT_NE(L.doString("require 'lanes'.configure{nb_user_keepers = 'gluh'}"), LuaError::OK); | ||
297 | } | ||
298 | |||
299 | // ################################################################################################# | ||
300 | |||
301 | TEST_F(Configure, NbUserKeepersNegative) | ||
302 | { | ||
303 | EXPECT_NE(L.doString("require 'lanes'.configure{nb_user_keepers = -1}"), LuaError::OK); | ||
304 | } | ||
305 | |||
306 | // ################################################################################################# | ||
307 | |||
308 | TEST_F(Configure, NbUserKeepersZero) | ||
309 | { | ||
310 | EXPECT_EQ(L.doString("require 'lanes'.configure{nb_user_keepers = 0}"), LuaError::OK); | ||
311 | } | ||
312 | |||
313 | // ################################################################################################# | ||
314 | |||
315 | TEST_F(Configure, NbUserKeepersHundred) | ||
316 | { | ||
317 | EXPECT_EQ(L.doString("require 'lanes'.configure{nb_user_keepers = 100}"), LuaError::OK); | ||
318 | } | ||
319 | |||
320 | // ################################################################################################# | ||
321 | |||
322 | TEST_F(Configure, NbUserKeepersHundredAndOne) | ||
323 | { | ||
324 | EXPECT_NE(L.doString("require 'lanes'.configure{nb_user_keepers = 101}"), LuaError::OK); | ||
325 | } | ||
326 | |||
327 | // ################################################################################################# | ||
328 | // ################################################################################################# | ||
329 | // on_state_create should be a function, either C or Lua, without upvalues | ||
330 | |||
331 | TEST_F(Configure, OnStateCreateTable) | ||
332 | { | ||
333 | EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = {}}"), LuaError::OK); | ||
334 | } | ||
335 | |||
336 | // ################################################################################################# | ||
337 | |||
338 | TEST_F(Configure, OnStateCreateString) | ||
339 | { | ||
340 | EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = 'gluh'}"), LuaError::OK); | ||
341 | } | ||
342 | |||
343 | // ################################################################################################# | ||
344 | |||
345 | TEST_F(Configure, OnStateCreateNumber) | ||
346 | { | ||
347 | EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = 1}"), LuaError::OK); | ||
348 | } | ||
349 | |||
350 | // ################################################################################################# | ||
351 | |||
352 | TEST_F(Configure, OnStateCreateFalse) | ||
353 | { | ||
354 | EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = false}"), LuaError::OK); | ||
355 | } | ||
356 | |||
357 | // ################################################################################################# | ||
358 | |||
359 | TEST_F(Configure, OnStateCreateTrue) | ||
360 | { | ||
361 | EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = true}"), LuaError::OK); | ||
362 | } | ||
363 | |||
364 | // ################################################################################################# | ||
365 | |||
366 | TEST_F(Configure, OnStateCreateUpvaluedFunction) | ||
367 | { | ||
368 | // on_state_create isn't called inside a Keeper state if it's a Lua function (which is good as print() doesn't exist there!) | ||
369 | EXPECT_EQ(L.doString("local print = print; require 'lanes'.configure{on_state_create = function() print 'hello' end}"), LuaError::OK); | ||
370 | } | ||
371 | |||
372 | // ################################################################################################# | ||
373 | |||
374 | TEST_F(Configure, OnStateCreateCFunction) | ||
375 | { | ||
376 | // funnily enough, in Lua 5.3, print() uses global tostring(), that doesn't exist in a keeper since we didn't open libs -> "attempt to call a nil value" | ||
377 | // conclusion, don't use print() as a fake on_state_create() callback! | ||
378 | // assert() should be fine since we pass a non-false argument to on_state_create | ||
379 | EXPECT_EQ(L.doString("require 'lanes'.configure{on_state_create = assert}"), LuaError::OK); | ||
380 | } | ||
381 | |||
382 | // ################################################################################################# | ||
383 | // ################################################################################################# | ||
384 | // shutdown_timeout should be a number in [0,3600] | ||
385 | |||
386 | TEST_F(Configure, ShutdownTimeoutTable) | ||
387 | { | ||
388 | EXPECT_NE(L.doString("require 'lanes'.configure{shutdown_timeout = {}}"), LuaError::OK); | ||
389 | } | ||
390 | |||
391 | // ################################################################################################# | ||
392 | |||
393 | TEST_F(Configure, ShutdownTimeoutString) | ||
394 | { | ||
395 | EXPECT_NE(L.doString("require 'lanes'.configure{shutdown_timeout = 'gluh'}"), LuaError::OK); | ||
396 | } | ||
397 | |||
398 | // ################################################################################################# | ||
399 | |||
400 | TEST_F(Configure, ShutdownTimeoutNegative) | ||
401 | { | ||
402 | EXPECT_NE(L.doString("require 'lanes'.configure{shutdown_timeout = -0.001}"), LuaError::OK); | ||
403 | } | ||
404 | |||
405 | // ################################################################################################# | ||
406 | |||
407 | TEST_F(Configure, ShutdownTimeoutZero) | ||
408 | { | ||
409 | EXPECT_EQ(L.doString("require 'lanes'.configure{shutdown_timeout = 0}"), LuaError::OK); | ||
410 | } | ||
411 | |||
412 | // ################################################################################################# | ||
413 | |||
414 | TEST_F(Configure, ShutdownTimeoutOne) | ||
415 | { | ||
416 | EXPECT_EQ(L.doString("require 'lanes'.configure{shutdown_timeout = 1}"), LuaError::OK); | ||
417 | } | ||
418 | |||
419 | // ################################################################################################# | ||
420 | |||
421 | TEST_F(Configure, ShutdownTimeoutHour) | ||
422 | { | ||
423 | EXPECT_EQ(L.doString("require 'lanes'.configure{shutdown_timeout = 3600}"), LuaError::OK); | ||
424 | } | ||
425 | |||
426 | // ################################################################################################# | ||
427 | |||
428 | TEST_F(Configure, ShutdownTimeoutTooLong) | ||
429 | { | ||
430 | EXPECT_NE(L.doString("require 'lanes'.configure{shutdown_timeout = 3600.001}"), LuaError::OK); | ||
431 | } | ||
432 | |||
433 | // ################################################################################################# | ||
434 | // ################################################################################################# | ||
435 | // strip_functions should be a boolean | ||
436 | |||
437 | TEST_F(Configure, StripFunctionsTable) | ||
438 | { | ||
439 | EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = {}}"), LuaError::OK); | ||
440 | } | ||
441 | |||
442 | // ################################################################################################# | ||
443 | |||
444 | TEST_F(Configure, StripFunctionsString) | ||
445 | { | ||
446 | EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = 'gluh'}"), LuaError::OK); | ||
447 | } | ||
448 | |||
449 | // ################################################################################################# | ||
450 | |||
451 | TEST_F(Configure, StripFunctionsNumber) | ||
452 | { | ||
453 | EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = 1}"), LuaError::OK); | ||
454 | } | ||
455 | |||
456 | // ################################################################################################# | ||
457 | |||
458 | TEST_F(Configure, StripFunctionsFunction) | ||
459 | { | ||
460 | EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = print}"), LuaError::OK); | ||
461 | } | ||
462 | |||
463 | // ################################################################################################# | ||
464 | |||
465 | TEST_F(Configure, StripFunctionsFalse) | ||
466 | { | ||
467 | EXPECT_EQ(L.doString("require 'lanes'.configure{strip_functions = false}"), LuaError::OK); | ||
468 | } | ||
469 | |||
470 | // ################################################################################################# | ||
471 | |||
472 | TEST_F(Configure, StripFunctionsTrue) | ||
473 | { | ||
474 | EXPECT_EQ(L.doString("require 'lanes'.configure{strip_functions = true}"), LuaError::OK); | ||
475 | } | ||
476 | |||
477 | // ################################################################################################# | ||
478 | // ################################################################################################# | ||
479 | // track_lanes should be a boolean | ||
480 | |||
481 | TEST_F(Configure, TrackLanesTable) | ||
482 | { | ||
483 | EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = {}}"), LuaError::OK); | ||
484 | } | ||
485 | |||
486 | // ################################################################################################# | ||
487 | |||
488 | TEST_F(Configure, TrackLanesString) | ||
489 | { | ||
490 | EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = 'gluh'}"), LuaError::OK); | ||
491 | } | ||
492 | |||
493 | // ################################################################################################# | ||
494 | |||
495 | TEST_F(Configure, TrackLanesNumber) | ||
496 | { | ||
497 | EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = 1}"), LuaError::OK); | ||
498 | } | ||
499 | |||
500 | // ################################################################################################# | ||
501 | |||
502 | TEST_F(Configure, TrackLanesFunction) | ||
503 | { | ||
504 | EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = print}"), LuaError::OK); | ||
505 | } | ||
506 | |||
507 | // ################################################################################################# | ||
508 | |||
509 | TEST_F(Configure, TrackLanesFalse) | ||
510 | { | ||
511 | EXPECT_EQ(L.doString("require 'lanes'.configure{track_lanes = false}"), LuaError::OK); | ||
512 | } | ||
513 | |||
514 | // ################################################################################################# | ||
515 | |||
516 | TEST_F(Configure, TrackLanesTrue) | ||
517 | { | ||
518 | EXPECT_EQ(L.doString("require 'lanes'.configure{track_lanes = true}"), LuaError::OK); | ||
519 | } | ||
520 | |||
521 | // ################################################################################################# | ||
522 | // ################################################################################################# | ||
523 | // verbose_errors should be a boolean | ||
524 | |||
525 | TEST_F(Configure, VerboseErrorsTable) | ||
526 | { | ||
527 | EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = {}}"), LuaError::OK); | ||
528 | } | ||
529 | |||
530 | // ################################################################################################# | ||
531 | |||
532 | TEST_F(Configure, VerboseErrorsString) | ||
533 | { | ||
534 | EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = 'gluh'}"), LuaError::OK); | ||
535 | } | ||
536 | |||
537 | // ################################################################################################# | ||
538 | |||
539 | TEST_F(Configure, VerboseErrorsNumber) | ||
540 | { | ||
541 | EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = 1}"), LuaError::OK); | ||
542 | } | ||
543 | |||
544 | // ################################################################################################# | ||
545 | |||
546 | TEST_F(Configure, VerboseErrorsFunction) | ||
547 | { | ||
548 | EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = print}"), LuaError::OK); | ||
549 | } | ||
550 | |||
551 | // ################################################################################################# | ||
552 | |||
553 | TEST_F(Configure, VerboseErrorsFalse) | ||
554 | { | ||
555 | EXPECT_EQ(L.doString("require 'lanes'.configure{verbose_errors = false}"), LuaError::OK); | ||
556 | } | ||
557 | |||
558 | // ################################################################################################# | ||
559 | |||
560 | TEST_F(Configure, VerboseErrorsTrue) | ||
561 | { | ||
562 | EXPECT_EQ(L.doString("require 'lanes'.configure{verbose_errors = true}"), LuaError::OK); | ||
563 | } | ||
564 | |||
565 | // ################################################################################################# | ||
566 | // ################################################################################################# | ||
567 | // with_timers should be a boolean | ||
568 | |||
569 | TEST_F(Configure, WithTimersTable) | ||
570 | { | ||
571 | EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = {}}"), LuaError::OK); | ||
572 | } | ||
573 | |||
574 | // ################################################################################################# | ||
575 | |||
576 | TEST_F(Configure, WithTimersString) | ||
577 | { | ||
578 | EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = 'gluh'}"), LuaError::OK); | ||
579 | } | ||
580 | |||
581 | // ################################################################################################# | ||
582 | |||
583 | TEST_F(Configure, WithTimersNumber) | ||
584 | { | ||
585 | EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = 1}"), LuaError::OK); | ||
586 | } | ||
587 | |||
588 | // ################################################################################################# | ||
589 | |||
590 | TEST_F(Configure, WithTimersFunction) | ||
591 | { | ||
592 | EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = print}"), LuaError::OK); | ||
593 | } | ||
594 | |||
595 | // ################################################################################################# | ||
596 | |||
597 | TEST_F(Configure, WithTimersFalse) | ||
598 | { | ||
599 | EXPECT_EQ(L.doString("require 'lanes'.configure{with_timers = false}"), LuaError::OK); | ||
600 | } | ||
601 | |||
602 | // ################################################################################################# | ||
603 | |||
604 | TEST_F(Configure, WithTimersTrue) | ||
605 | { | ||
606 | EXPECT_EQ(L.doString("require 'lanes'.configure{with_timers = true}"), LuaError::OK); | ||
607 | } | ||
608 | |||
609 | // ################################################################################################# | ||
610 | // ################################################################################################# | ||
611 | // any unknown setting should be rejected | ||
612 | |||
613 | TEST_F(Configure, UnknownSettingTable) | ||
614 | { | ||
615 | EXPECT_NE(L.doString("require 'lanes'.configure{[{}] = {}}"), LuaError::OK); | ||
616 | } | ||
617 | |||
618 | // ################################################################################################# | ||
619 | |||
620 | TEST_F(Configure, UnknownSettingBool) | ||
621 | { | ||
622 | EXPECT_NE(L.doString("require 'lanes'.configure{[true] = 'gluh'}"), LuaError::OK); | ||
623 | } | ||
624 | |||
625 | // ################################################################################################# | ||
626 | |||
627 | TEST_F(Configure, UnknownSettingFunction) | ||
628 | { | ||
629 | EXPECT_NE(L.doString("require 'lanes'.configure{[function() end] = 1}"), LuaError::OK); | ||
630 | } | ||
631 | |||
632 | // ################################################################################################# | ||
633 | |||
634 | TEST_F(Configure, UnknownSettingNumber) | ||
635 | { | ||
636 | EXPECT_NE(L.doString("require 'lanes'.configure{[1] = function() end}"), LuaError::OK); | ||
637 | } | ||
638 | |||
639 | // ################################################################################################# | ||
640 | |||
641 | TEST_F(Configure, UnknownSettingString) | ||
642 | { | ||
643 | EXPECT_NE(L.doString("require 'lanes'.configure{['gluh'] = false}"), LuaError::OK); | ||
644 | } | ||
645 | |||
646 | // ################################################################################################# | ||
647 | // ################################################################################################# | ||
648 | |||
649 | TEST(Lanes, Finally) | ||
650 | { | ||
651 | { | ||
652 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
653 | // we need Lanes to be up. Since we run several 'scripts', we store it as a global | ||
654 | EXPECT_EQ(S.doString("lanes = require 'lanes'"), LuaError::OK) << S; | ||
655 | // we can set a function | ||
656 | EXPECT_EQ(S.doString("lanes.finally(function() end)"), LuaError::OK) << S; | ||
657 | // we can clear it | ||
658 | EXPECT_EQ(S.doString("lanes.finally(nil)"), LuaError::OK) << S; | ||
659 | // we can set a new one | ||
660 | EXPECT_EQ(S.doString("lanes.finally(function() end)"), LuaError::OK) << S; | ||
661 | // we can replace an existing function | ||
662 | EXPECT_EQ(S.doString("lanes.finally(error)"), LuaError::OK) << S; | ||
663 | // even if the finalizer throws a Lua error, it shouldn't crash anything | ||
664 | ASSERT_NO_FATAL_FAILURE(S.close()); | ||
665 | ASSERT_EQ(S.finalizerWasCalled, false); | ||
666 | } | ||
667 | |||
668 | { | ||
669 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
670 | |||
671 | // we need Lanes to be up. Since we run several 'scripts', we store it as a global | ||
672 | EXPECT_EQ(S.doString("lanes = require 'lanes'"), LuaError::OK) << S; | ||
673 | // works because we have package.preload.fixture = luaopen_fixture | ||
674 | EXPECT_EQ(S.doString("fixture = require 'fixture'"), LuaError::OK) << S; | ||
675 | // set our detectable finalizer | ||
676 | EXPECT_EQ(S.doString("lanes.finally(fixture.throwing_finalizer)"), LuaError::OK) << S; | ||
677 | // even if the finalizer can request a C++ exception, it shouldn't do it just now since we have no dangling lane | ||
678 | ASSERT_NO_THROW(S.close()) << S; | ||
679 | // the finalizer should be called | ||
680 | ASSERT_EQ(S.finalizerWasCalled, true); | ||
681 | } | ||
682 | } | ||
683 | |||
684 | // ################################################################################################# | ||
685 | |||
686 | namespace | ||
687 | { | ||
688 | namespace local | ||
689 | { | ||
690 | std::atomic_int OnStateCreateCallsCount{}; | ||
691 | static int on_state_create(lua_State* const L_) | ||
692 | { | ||
693 | OnStateCreateCallsCount.fetch_add(1, std::memory_order_relaxed); | ||
694 | return 0; | ||
695 | } | ||
696 | |||
697 | } // namespace local | ||
698 | } | ||
699 | |||
700 | class OnStateCreate : public ::testing::Test | ||
701 | { | ||
702 | protected: | ||
703 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
704 | |||
705 | void SetUp() override | ||
706 | { | ||
707 | local::OnStateCreateCallsCount.store(0, std::memory_order_relaxed); | ||
708 | } | ||
709 | |||
710 | void TearDown() override | ||
711 | { | ||
712 | local::OnStateCreateCallsCount.store(0, std::memory_order_relaxed); | ||
713 | } | ||
714 | }; | ||
715 | |||
716 | // ################################################################################################# | ||
717 | |||
718 | TEST_F(OnStateCreate, CalledInKeepers) | ||
719 | { | ||
720 | // _G.on_state_create = on_state_create; | ||
721 | lua_pushcfunction(S, local::on_state_create); | ||
722 | lua_setglobal(S, "on_state_create"); | ||
723 | ASSERT_EQ(S.doString("lanes = require 'lanes'.configure{on_state_create = on_state_create, nb_user_keepers = 3}"), LuaError::OK) << S; | ||
724 | ASSERT_EQ(local::OnStateCreateCallsCount.load(std::memory_order_relaxed), 4) << "on_state_create should have been called once in each Keeper state"; | ||
725 | } | ||
726 | |||
727 | // ################################################################################################# | ||
728 | |||
729 | TEST_F(OnStateCreate, CalledInLane) | ||
730 | { | ||
731 | // _G.on_state_create = on_state_create; | ||
732 | lua_pushcfunction(S, local::on_state_create); | ||
733 | lua_setglobal(S, "on_state_create"); | ||
734 | ASSERT_EQ(S.doString("lanes = require 'lanes'.configure{on_state_create = on_state_create, with_timers = true}"), LuaError::OK) << S; | ||
735 | ASSERT_EQ(local::OnStateCreateCallsCount.load(std::memory_order_relaxed), 2) << "on_state_create should have been called in the Keeper state and the timer lane"; | ||
736 | } | ||
737 | |||
738 | // ################################################################################################# | ||
739 | |||
740 | TEST_F(OnStateCreate, CanPackagePreload) | ||
741 | { | ||
742 | // a C function for which we can test it was called | ||
743 | static bool _doStuffWasCalled{}; | ||
744 | static auto _doStuff = +[](lua_State* const L_) { | ||
745 | _doStuffWasCalled = true; | ||
746 | return 0; | ||
747 | }; | ||
748 | |||
749 | // a module that exports the above function | ||
750 | static auto _luaopen_Stuff = +[](lua_State* const L_) { | ||
751 | lua_newtable(L_); // t | ||
752 | lua_pushstring(L_, "DoStuffInC"); // t "DoStuffInC" | ||
753 | lua_pushcfunction(L_, _doStuff); // t "DoStuffInC" _doStuff | ||
754 | lua_settable(L_, -3); // t | ||
755 | return 1; | ||
756 | }; | ||
757 | |||
758 | // a function that installs the module loader function in package.preload | ||
759 | auto _on_state_create = [](lua_State* const L_) { | ||
760 | |||
761 | lua_getglobal(L_, "package"); // package | ||
762 | if (lua_istable(L_, -1)) { | ||
763 | lua_getfield(L_, -1, "preload"); // package package.preload | ||
764 | if (lua_istable(L_, -1)) { | ||
765 | lua_pushcfunction(L_, _luaopen_Stuff); // package package.preload luaopen_Stuff | ||
766 | lua_setfield(L_, -2, "Stuff"); // package package.preload | ||
767 | } | ||
768 | lua_pop(L_, 1); // package | ||
769 | } | ||
770 | lua_pop(L_, 1); // | ||
771 | |||
772 | return 0; | ||
773 | }; | ||
774 | |||
775 | // _G.on_state_create = on_state_create; | ||
776 | lua_pushcfunction(S, _on_state_create); | ||
777 | lua_setglobal(S, "on_state_create"); | ||
778 | |||
779 | ASSERT_EQ(S.doString("lanes = require 'lanes'.configure{on_state_create = on_state_create}"), LuaError::OK) << S; | ||
780 | |||
781 | // launch a Lane that requires the module. It should succeed because _on_state_create was called and made it possible | ||
782 | std::string_view const _script{ | ||
783 | " f = lanes.gen('*'," | ||
784 | " function()" | ||
785 | " local Stuff = require ('Stuff')" | ||
786 | " Stuff.DoStuffInC()" | ||
787 | " return true" | ||
788 | " end)" | ||
789 | " f():join()" // start the lane and block until it terminates | ||
790 | }; | ||
791 | ASSERT_EQ(S.doString(_script), LuaError::OK) << S; | ||
792 | ASSERT_TRUE(_doStuffWasCalled); | ||
793 | } | ||
diff --git a/unit_tests/lane_tests.cpp b/unit_tests/lane_tests.cpp new file mode 100644 index 0000000..14a327f --- /dev/null +++ b/unit_tests/lane_tests.cpp | |||
@@ -0,0 +1,209 @@ | |||
1 | #include "_pch.hpp" | ||
2 | #include "shared.h" | ||
3 | |||
4 | // ################################################################################################# | ||
5 | // ################################################################################################# | ||
6 | |||
7 | class LaneTests : public ::testing::Test | ||
8 | { | ||
9 | protected: | ||
10 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
11 | |||
12 | void SetUp() override | ||
13 | { | ||
14 | std::ignore = S.doString("lanes = require 'lanes'.configure()"); | ||
15 | } | ||
16 | }; | ||
17 | |||
18 | // ################################################################################################# | ||
19 | |||
20 | TEST_F(LaneTests, GeneratorCreation) | ||
21 | { | ||
22 | // no parameter is bad | ||
23 | EXPECT_NE(S.doString("lanes.gen()"), LuaError::OK); | ||
24 | |||
25 | // minimal generator needs a function | ||
26 | EXPECT_EQ(S.doString("lanes.gen(function() end)"), LuaError::OK) << S; | ||
27 | |||
28 | // acceptable parameters for the generator are strings, tables, nil, followed by the function body | ||
29 | EXPECT_EQ(S.doString("lanes.gen(nil, function() end)"), LuaError::OK) << S; | ||
30 | EXPECT_EQ(S.doString("lanes.gen('', function() end)"), LuaError::OK) << S; | ||
31 | EXPECT_EQ(S.doString("lanes.gen({}, function() end)"), LuaError::OK) << S; | ||
32 | EXPECT_EQ(S.doString("lanes.gen('', {}, function() end)"), LuaError::OK) << S; | ||
33 | EXPECT_EQ(S.doString("lanes.gen({}, '', function() end)"), LuaError::OK) << S; | ||
34 | EXPECT_EQ(S.doString("lanes.gen('', '', function() end)"), LuaError::OK) << S; | ||
35 | EXPECT_EQ(S.doString("lanes.gen({}, {}, function() end)"), LuaError::OK) << S; | ||
36 | |||
37 | // anything different should fail: booleans, numbers, any userdata | ||
38 | EXPECT_NE(S.doString("lanes.gen(false, function() end)"), LuaError::OK); | ||
39 | EXPECT_NE(S.doString("lanes.gen(true, function() end)"), LuaError::OK); | ||
40 | EXPECT_NE(S.doString("lanes.gen(42, function() end)"), LuaError::OK); | ||
41 | EXPECT_NE(S.doString("lanes.gen(io.stdin, function() end)"), LuaError::OK); | ||
42 | EXPECT_NE(S.doString("lanes.gen(lanes.linda(), function() end)"), LuaError::OK); | ||
43 | EXPECT_NE(S.doString("lanes.gen(lanes.linda():deep(), function() end)"), LuaError::OK); | ||
44 | |||
45 | // even if parameter types are correct, the function must come last | ||
46 | EXPECT_NE(S.doString("lanes.gen(function() end, '')"), LuaError::OK); | ||
47 | |||
48 | // the strings should only list "known base libraries", in any order, or "*" | ||
49 | // if the particular Lua flavor we build for doesn't support them, they raise an error unless postfixed by '?' | ||
50 | EXPECT_EQ(S.doString("lanes.gen('base', function() end)"), LuaError::OK) << S; | ||
51 | |||
52 | // bit, ffi, jit are LuaJIT-specific | ||
53 | #if LUAJIT_FLAVOR() == 0 | ||
54 | EXPECT_NE(S.doString("lanes.gen('bit,ffi,jit', function() end)"), LuaError::OK); | ||
55 | EXPECT_EQ(S.doString("lanes.gen('bit?,ffi?,jit?', function() end)"), LuaError::OK) << S; | ||
56 | #endif // LUAJIT_FLAVOR() | ||
57 | |||
58 | // bit32 library existed only in Lua 5.2, there is still a loader that will raise an error in Lua 5.3 | ||
59 | #if LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 | ||
60 | EXPECT_EQ(S.doString("lanes.gen('bit32', function() end)"), LuaError::OK) << S; | ||
61 | #else // LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 | ||
62 | EXPECT_NE(S.doString("lanes.gen('bit32', function() end)"), LuaError::OK); | ||
63 | EXPECT_EQ(S.doString("lanes.gen('bit32?', function() end)"), LuaError::OK) << S; | ||
64 | #endif // LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 | ||
65 | |||
66 | // coroutine library appeared with Lua 5.2 | ||
67 | #if LUA_VERSION_NUM == 501 | ||
68 | EXPECT_NE(S.doString("lanes.gen('coroutine', function() end)"), LuaError::OK); | ||
69 | EXPECT_EQ(S.doString("lanes.gen('coroutine?', function() end)"), LuaError::OK) << S; | ||
70 | #endif // LUA_VERSION_NUM == 501 | ||
71 | |||
72 | EXPECT_EQ(S.doString("lanes.gen('debug', function() end)"), LuaError::OK) << S; | ||
73 | EXPECT_EQ(S.doString("lanes.gen('io', function() end)"), LuaError::OK) << S; | ||
74 | EXPECT_EQ(S.doString("lanes.gen('math', function() end)"), LuaError::OK) << S; | ||
75 | EXPECT_EQ(S.doString("lanes.gen('os', function() end)"), LuaError::OK) << S; | ||
76 | EXPECT_EQ(S.doString("lanes.gen('package', function() end)"), LuaError::OK) << S; | ||
77 | EXPECT_EQ(S.doString("lanes.gen('string', function() end)"), LuaError::OK) << S; | ||
78 | EXPECT_EQ(S.doString("lanes.gen('table', function() end)"), LuaError::OK) << S; | ||
79 | |||
80 | // utf8 library appeared with Lua 5.3 | ||
81 | #if LUA_VERSION_NUM < 503 | ||
82 | EXPECT_NE(S.doString("lanes.gen('utf8', function() end)"), LuaError::OK); | ||
83 | EXPECT_EQ(S.doString("lanes.gen('utf8?', function() end)"), LuaError::OK) << S; | ||
84 | #endif // LUA_VERSION_NUM < 503 | ||
85 | |||
86 | EXPECT_EQ(S.doString("lanes.gen('lanes.core', function() end)"), LuaError::OK) << S; | ||
87 | // "*" repeated or combined with anything else is forbidden | ||
88 | EXPECT_NE(S.doString("lanes.gen('*', '*', function() end)"), LuaError::OK); | ||
89 | EXPECT_NE(S.doString("lanes.gen('base', '*', function() end)"), LuaError::OK); | ||
90 | // unknown names are forbidden | ||
91 | EXPECT_NE(S.doString("lanes.gen('Base', function() end)"), LuaError::OK); | ||
92 | // repeating the same library more than once is forbidden | ||
93 | EXPECT_NE(S.doString("lanes.gen('base,base', function() end)"), LuaError::OK); | ||
94 | } | ||
95 | |||
96 | // ################################################################################################# | ||
97 | |||
98 | TEST_F(LaneTests, UncooperativeShutdown) | ||
99 | { | ||
100 | // prepare a callback for lanes.finally() | ||
101 | static bool _wasCalled{}; | ||
102 | static bool _allLanesTerminated{}; | ||
103 | auto _finallyCB{ +[](lua_State* const L_) { _wasCalled = true; _allLanesTerminated = lua_toboolean(L_, 1); return 0; } }; | ||
104 | lua_pushcfunction(S, _finallyCB); | ||
105 | lua_setglobal(S, "finallyCB"); | ||
106 | // start a lane that lasts a long time | ||
107 | std::string_view const script{ | ||
108 | " lanes.finally(finallyCB)" | ||
109 | " print ('in Master')" | ||
110 | " f = lanes.gen('*'," | ||
111 | " {name = 'auto'}," | ||
112 | " function()" | ||
113 | " for i = 1,1e37 do end" // no cooperative cancellation checks here! | ||
114 | " end)" | ||
115 | " f()" | ||
116 | }; | ||
117 | ASSERT_EQ(S.doString(script), LuaError::OK) << S; | ||
118 | // close the state before the lane ends. | ||
119 | // since we don't wait at all, it is possible that the OS thread for the lane hasn't even started at that point | ||
120 | S.close(); | ||
121 | // the finally handler should have been called, and told all lanes are stopped | ||
122 | ASSERT_EQ(_wasCalled, true) << S; | ||
123 | ASSERT_EQ(_allLanesTerminated, true) << S; | ||
124 | } | ||
125 | |||
126 | // ################################################################################################# | ||
127 | // ################################################################################################# | ||
128 | |||
129 | class LaneCancel : public ::testing::Test | ||
130 | { | ||
131 | protected: | ||
132 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
133 | |||
134 | void SetUp() override | ||
135 | { | ||
136 | // need the timers so that there is a lane running on which we can operate | ||
137 | std::ignore = S.doString("timer_lane = require 'lanes'.configure{with_timers = true}.timer_lane"); | ||
138 | } | ||
139 | }; | ||
140 | |||
141 | // ################################################################################################# | ||
142 | |||
143 | TEST_F(LaneCancel, FailsOnBadArguments) | ||
144 | { | ||
145 | //FAIL() << "Make sure the failures fail the way we expect them"; | ||
146 | // make sure we have the timer lane and its cancel method handy | ||
147 | ASSERT_EQ(S.doString("assert(timer_lane and timer_lane.cancel)"), LuaError::OK); | ||
148 | // as well as the fixture module | ||
149 | ASSERT_EQ(S.doString("fixture = require 'fixture'"), LuaError::OK) << S; | ||
150 | |||
151 | // cancel operation must be a known string | ||
152 | ASSERT_NE(S.doString("timer_lane:cancel('gleh')"), LuaError::OK); | ||
153 | ASSERT_NE(S.doString("timer_lane:cancel(function() end)"), LuaError::OK); | ||
154 | ASSERT_NE(S.doString("timer_lane:cancel({})"), LuaError::OK); | ||
155 | ASSERT_NE(S.doString("timer_lane:cancel(fixture.newuserdata())"), LuaError::OK); | ||
156 | ASSERT_NE(S.doString("timer_lane:cancel(fixture.newlightuserdata())"), LuaError::OK); | ||
157 | |||
158 | // cancel doesn't expect additional non-number/bool arguments after the mode | ||
159 | ASSERT_NE(S.doString("timer_lane:cancel('soft', 'gleh')"), LuaError::OK); | ||
160 | ASSERT_NE(S.doString("timer_lane:cancel('soft', function() end)"), LuaError::OK); | ||
161 | ASSERT_NE(S.doString("timer_lane:cancel('soft', {})"), LuaError::OK); | ||
162 | ASSERT_NE(S.doString("timer_lane:cancel('soft', fixture.newuserdata())"), LuaError::OK); | ||
163 | ASSERT_NE(S.doString("timer_lane:cancel('soft', fixture.newlightuserdata())"), LuaError::OK); | ||
164 | |||
165 | // hook-based cancellation expects a number for the count. IOW, a bool is not valid | ||
166 | ASSERT_NE(S.doString("timer_lane:cancel('call', true)"), LuaError::OK); | ||
167 | ASSERT_NE(S.doString("timer_lane:cancel('ret', true)"), LuaError::OK); | ||
168 | ASSERT_NE(S.doString("timer_lane:cancel('line', true)"), LuaError::OK); | ||
169 | ASSERT_NE(S.doString("timer_lane:cancel('count', true)"), LuaError::OK); | ||
170 | ASSERT_NE(S.doString("timer_lane:cancel('all', true)"), LuaError::OK); | ||
171 | |||
172 | // non-hook should only have one number after the mode (the timeout), else it means we have a count | ||
173 | ASSERT_NE(S.doString("timer_lane:cancel('hard', 10, 10)"), LuaError::OK); | ||
174 | |||
175 | // extra arguments are not accepted either | ||
176 | ASSERT_NE(S.doString("timer_lane:cancel('hard', 10, true, 10)"), LuaError::OK); | ||
177 | ASSERT_NE(S.doString("timer_lane:cancel('call', 10, 10, 10)"), LuaError::OK); | ||
178 | ASSERT_NE(S.doString("timer_lane:cancel('line', 10, 10, true, 10)"), LuaError::OK); | ||
179 | |||
180 | // out-of-range hook count is not valid | ||
181 | ASSERT_NE(S.doString("timer_lane:cancel('call', -1)"), LuaError::OK); | ||
182 | ASSERT_NE(S.doString("timer_lane:cancel('call', 0)"), LuaError::OK); | ||
183 | |||
184 | // out-of-range duration is not valid | ||
185 | ASSERT_NE(S.doString("timer_lane:cancel('soft', -1)"), LuaError::OK); | ||
186 | } | ||
187 | |||
188 | // ################################################################################################# | ||
189 | // ################################################################################################# | ||
190 | |||
191 | INSTANTIATE_TEST_CASE_P( | ||
192 | LaneScriptedTests, | ||
193 | UnitTestRunner, | ||
194 | ::testing::Values( | ||
195 | FileRunnerParam{ PUC_LUA_ONLY("lane/cooperative_shutdown"), TestType::AssertNoLuaError }, // 0 | ||
196 | FileRunnerParam{ "lane/uncooperative_shutdown", TestType::AssertThrows }, | ||
197 | FileRunnerParam{ "lane/tasking_basic", TestType::AssertNoLuaError }, // 2 | ||
198 | FileRunnerParam{ "lane/tasking_cancelling", TestType::AssertNoLuaError }, // 3 | ||
199 | FileRunnerParam{ "lane/tasking_comms_criss_cross", TestType::AssertNoLuaError }, // 4 | ||
200 | FileRunnerParam{ "lane/tasking_communications", TestType::AssertNoLuaError }, | ||
201 | FileRunnerParam{ "lane/tasking_error", TestType::AssertNoLuaError }, // 6 | ||
202 | FileRunnerParam{ "lane/tasking_join_test", TestType::AssertNoLuaError }, // 7 | ||
203 | FileRunnerParam{ "lane/tasking_send_receive_code", TestType::AssertNoLuaError }, | ||
204 | FileRunnerParam{ "lane/stdlib_naming", TestType::AssertNoLuaError }, | ||
205 | FileRunnerParam{ "coro/basics", TestType::AssertNoLuaError }, // 10 | ||
206 | FileRunnerParam{ "coro/error_handling", TestType::AssertNoLuaError } | ||
207 | ) | ||
208 | //,[](::testing::TestParamInfo<FileRunnerParam> const& info_) { return std::string{ info_.param.script };} | ||
209 | ); | ||
diff --git a/unit_tests/legacy_tests.cpp b/unit_tests/legacy_tests.cpp new file mode 100644 index 0000000..2d88190 --- /dev/null +++ b/unit_tests/legacy_tests.cpp | |||
@@ -0,0 +1,79 @@ | |||
1 | #include "_pch.hpp" | ||
2 | #include "shared.h" | ||
3 | |||
4 | #define RUN_LEGACY_TESTS 1 | ||
5 | #if RUN_LEGACY_TESTS | ||
6 | |||
7 | // ################################################################################################# | ||
8 | |||
9 | class LegacyTestRunner : public FileRunner | ||
10 | { | ||
11 | public: | ||
12 | LegacyTestRunner() | ||
13 | { | ||
14 | [[maybe_unused]] std::filesystem::path const _current{ std::filesystem::current_path() }; | ||
15 | std::filesystem::path _path{ R"(.\lanes\tests\)" }; | ||
16 | root = std::filesystem::canonical(_path).generic_string(); | ||
17 | // I need to append that path to the list of locations where modules can be required | ||
18 | // so that the legacy scripts can require"assert" and find assert.lua | ||
19 | std::string _script{"package.path = package.path.."}; | ||
20 | _script += "';"; | ||
21 | _script += root; | ||
22 | _script += "/?.lua'"; | ||
23 | std::ignore = L.doString(_script.c_str()); | ||
24 | } | ||
25 | }; | ||
26 | |||
27 | TEST_P(LegacyTestRunner, LegacyTest) | ||
28 | { | ||
29 | FileRunnerParam const& _param = GetParam(); | ||
30 | switch (_param.test) { | ||
31 | case TestType::AssertNoLuaError: | ||
32 | ASSERT_EQ(L.doFile(root, _param.script), LuaError::OK) << L; | ||
33 | break; | ||
34 | case TestType::AssertNoThrow: | ||
35 | ASSERT_NO_THROW((std::ignore = L.doFile(root, _param.script), L.close())) << L; | ||
36 | break; | ||
37 | case TestType::AssertThrows: | ||
38 | ASSERT_THROW((std::ignore = L.doFile(root, _param.script), L.close()), std::logic_error) << L; | ||
39 | break; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | INSTANTIATE_TEST_CASE_P( | ||
44 | LegacyTests, | ||
45 | LegacyTestRunner, | ||
46 | ::testing::Values( | ||
47 | "appendud" // 0 | ||
48 | , "atexit" // 1 | ||
49 | , "atomic" // 2 | ||
50 | , "basic" // 3 | ||
51 | , "cancel" // 4 | ||
52 | , "cyclic" // 5 | ||
53 | , "deadlock" // 6 | ||
54 | , "errhangtest" // 7 | ||
55 | , "error" // 8 | ||
56 | , "fibonacci" // 9 | ||
57 | , "fifo" // 10 | ||
58 | , "finalizer" // 11 | ||
59 | , "func_is_string" // 12 | ||
60 | , "irayo_closure" // 13 | ||
61 | , "irayo_recursive" // 14 | ||
62 | , "keeper" // 15 | ||
63 | //, "linda_perf" | ||
64 | , LUA54_ONLY("manual_register") // 16: uses lfs module, currently not available for non-5.4 flavors | ||
65 | , "nameof" // 17 | ||
66 | , "objects" // 18 | ||
67 | , "package" // 19 | ||
68 | , "pingpong" // 20 | ||
69 | , "recursive" // 21 | ||
70 | , "require" // 22 | ||
71 | , "rupval" // 23 | ||
72 | , "timer" // 24 | ||
73 | , LUA54_ONLY("tobeclosed") // 25 | ||
74 | , "track_lanes" // 26 | ||
75 | ) | ||
76 | //, [](::testing::TestParamInfo<FileRunnerParam> const& info_) { return info_.param.script.empty() ? "N/A": std::string{ info_.param.script }; } | ||
77 | ); | ||
78 | |||
79 | #endif // RUN_LEGACY_TESTS \ No newline at end of file | ||
diff --git a/unit_tests/linda_tests.cpp b/unit_tests/linda_tests.cpp new file mode 100644 index 0000000..b3bad66 --- /dev/null +++ b/unit_tests/linda_tests.cpp | |||
@@ -0,0 +1,342 @@ | |||
1 | #include "_pch.hpp" | ||
2 | #include "shared.h" | ||
3 | |||
4 | // ################################################################################################# | ||
5 | |||
6 | class OneKeeperLindaTests : public ::testing::Test | ||
7 | { | ||
8 | protected: | ||
9 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
10 | |||
11 | void SetUp() override | ||
12 | { | ||
13 | std::ignore = S.doString("lanes = require 'lanes'"); | ||
14 | } | ||
15 | }; | ||
16 | |||
17 | TEST_F(OneKeeperLindaTests, LindaCreation) | ||
18 | { | ||
19 | // no parameters is ok | ||
20 | EXPECT_EQ(S.doString("lanes.linda()"), LuaError::OK) << S; | ||
21 | EXPECT_NE(S.doStringAndRet("return tostring(lanes.linda())"), R"===(Linda: <not a string>)===") << S; // unspecified name should not result in <not a string> | ||
22 | |||
23 | // since we have only one keeper, only group 0 is authorized | ||
24 | EXPECT_NE(S.doString("lanes.linda(-1)"), LuaError::OK); | ||
25 | EXPECT_EQ(S.doString("lanes.linda(0)"), LuaError::OK) << S; | ||
26 | EXPECT_NE(S.doString("lanes.linda(1)"), LuaError::OK); | ||
27 | |||
28 | // any name is ok | ||
29 | EXPECT_EQ(S.doString("lanes.linda('')"), LuaError::OK) << S; // an empty name results in a string conversion of the form "Linda: <some hex value>" that we can't test (but it works) | ||
30 | EXPECT_EQ(S.doStringAndRet("return tostring(lanes.linda('short name'))"), R"===(Linda: short name)===") << S; | ||
31 | EXPECT_EQ(S.doStringAndRet("return tostring(lanes.linda('very very very very very very long name'))"), R"===(Linda: very very very very very very long name)===") << S; | ||
32 | EXPECT_EQ(S.doStringAndRet("return tostring(lanes.linda('auto'))"), R"===(Linda: [string "return tostring(lanes.linda('auto'))"]:1)===") << S; | ||
33 | |||
34 | if constexpr (LUA_VERSION_NUM == 504) { | ||
35 | // a function is acceptable as a __close handler | ||
36 | EXPECT_EQ(S.doString("local l <close> = lanes.linda(function() end)"), LuaError::OK) << S; | ||
37 | // a callable table too (a callable full userdata as well, but I have none here) | ||
38 | EXPECT_EQ(S.doString("local l <close> = lanes.linda(setmetatable({}, {__call = function() end}))"), LuaError::OK) << S; | ||
39 | // if the function raises an error, we should get it | ||
40 | EXPECT_NE(S.doString("local l <close> = lanes.linda(function() error 'gluh' end)"), LuaError::OK); | ||
41 | } else { | ||
42 | // no __close support before Lua 5.4 | ||
43 | EXPECT_NE(S.doString("lanes.linda(function() end)"), LuaError::OK); | ||
44 | EXPECT_NE(S.doString("lanes.linda(setmetatable({}, {__call = function() end}))"), LuaError::OK); | ||
45 | } | ||
46 | |||
47 | // mixing parameters in any order is ok: 2 out of 3 | ||
48 | EXPECT_EQ(S.doString("lanes.linda(0, 'name')"), LuaError::OK) << S; | ||
49 | EXPECT_EQ(S.doString("lanes.linda('name', 0)"), LuaError::OK) << S; | ||
50 | if constexpr (LUA_VERSION_NUM == 504) { | ||
51 | EXPECT_EQ(S.doString("lanes.linda(0, function() end)"), LuaError::OK) << S; | ||
52 | EXPECT_EQ(S.doString("lanes.linda(function() end, 0)"), LuaError::OK) << S; | ||
53 | EXPECT_EQ(S.doString("lanes.linda('name', function() end)"), LuaError::OK) << S; | ||
54 | EXPECT_EQ(S.doString("lanes.linda(function() end, 'name')"), LuaError::OK) << S; | ||
55 | } | ||
56 | |||
57 | // mixing parameters in any order is ok: 3 out of 3 | ||
58 | if constexpr (LUA_VERSION_NUM == 504) { | ||
59 | EXPECT_EQ(S.doString("lanes.linda(0, 'name', function() end)"), LuaError::OK) << S; | ||
60 | EXPECT_EQ(S.doString("lanes.linda(0, function() end, 'name')"), LuaError::OK) << S; | ||
61 | EXPECT_EQ(S.doString("lanes.linda('name', 0, function() end)"), LuaError::OK) << S; | ||
62 | EXPECT_EQ(S.doString("lanes.linda('name', function() end, 0)"), LuaError::OK) << S; | ||
63 | EXPECT_EQ(S.doString("lanes.linda(function() end, 0, 'name')"), LuaError::OK) << S; | ||
64 | EXPECT_EQ(S.doString("lanes.linda(function() end, 'name', 0)"), LuaError::OK) << S; | ||
65 | } | ||
66 | |||
67 | // unsupported parameters should fail | ||
68 | EXPECT_NE(S.doString("lanes.linda(true)"), LuaError::OK); | ||
69 | EXPECT_NE(S.doString("lanes.linda(false)"), LuaError::OK); | ||
70 | // uncallable table or full userdata | ||
71 | EXPECT_NE(S.doString("lanes.linda({})"), LuaError::OK); | ||
72 | EXPECT_NE(S.doString("lanes.linda(lanes.linda())"), LuaError::OK); | ||
73 | } | ||
74 | |||
75 | // ################################################################################################# | ||
76 | |||
77 | TEST_F(OneKeeperLindaTests, LindaIndexing) | ||
78 | { | ||
79 | // indexing the linda with an unknown string key should fail | ||
80 | EXPECT_NE(S.doString("return lanes.linda().gouikra"), LuaError::OK) << S; | ||
81 | // indexing the linda with an unsupported key type should fail | ||
82 | EXPECT_NE(S.doString("return lanes.linda()[5]"), LuaError::OK) << S; | ||
83 | EXPECT_NE(S.doString("return lanes.linda()[false]"), LuaError::OK) << S; | ||
84 | EXPECT_NE(S.doString("return lanes.linda()[{}]"), LuaError::OK) << S; | ||
85 | EXPECT_NE(S.doString("return lanes.linda()[function() end]"), LuaError::OK) << S; | ||
86 | } | ||
87 | |||
88 | // ################################################################################################# | ||
89 | |||
90 | TEST_F(OneKeeperLindaTests, LindaSendTimeoutValidation) | ||
91 | { | ||
92 | // timeout checks | ||
93 | // linda:send() should fail if the timeout is bad | ||
94 | EXPECT_NE(S.doString("lanes.linda():send(-1, 'k', 'v')"), LuaError::OK); | ||
95 | // any positive value is ok | ||
96 | EXPECT_EQ(S.doString("lanes.linda():send(0, 'k', 'v')"), LuaError::OK) << S; | ||
97 | EXPECT_EQ(S.doString("lanes.linda():send(1e20, 'k', 'v')"), LuaError::OK) << S; | ||
98 | // nil too (same as 'forever') | ||
99 | EXPECT_EQ(S.doString("lanes.linda():send(nil, 'k', 'v')"), LuaError::OK) << S; | ||
100 | } | ||
101 | |||
102 | // ################################################################################################# | ||
103 | |||
104 | TEST_F(OneKeeperLindaTests, LindaSendFailsOnBadKeys) | ||
105 | { | ||
106 | // key checks | ||
107 | // linda:send() should fail if the key is unsupported (nil, table, function, full userdata, reserved light userdata) | ||
108 | EXPECT_NE(S.doString("lanes.linda():send(0, nil, 'v')"), LuaError::OK); | ||
109 | EXPECT_NE(S.doString("lanes.linda():send(0, {}, 'v')"), LuaError::OK); | ||
110 | EXPECT_NE(S.doString("lanes.linda():send(0, function() end, 'v')"), LuaError::OK); | ||
111 | EXPECT_NE(S.doString("lanes.linda():send(0, io.stdin, 'v')"), LuaError::OK); | ||
112 | EXPECT_NE(S.doString("lanes.linda():send(0, lanes.null, 'v')"), LuaError::OK); | ||
113 | EXPECT_NE(S.doString("lanes.linda():send(0, lanes.cancel_error, 'v')"), LuaError::OK); | ||
114 | EXPECT_NE(S.doString("local l = lanes.linda(); l:send(0, l.batched, 'v')"), LuaError::OK); | ||
115 | } | ||
116 | |||
117 | // ################################################################################################# | ||
118 | |||
119 | TEST_F(OneKeeperLindaTests, LindaSendSucceedsOnSupportedKeys) | ||
120 | { | ||
121 | // supported keys are ok: boolean, number, string, light userdata, deep userdata | ||
122 | EXPECT_EQ(S.doString("lanes.linda():send(0, true, 'v')"), LuaError::OK) << S; | ||
123 | EXPECT_EQ(S.doString("lanes.linda():send(0, false, 'v')"), LuaError::OK) << S; | ||
124 | EXPECT_EQ(S.doString("lanes.linda():send(0, 99, 'v')"), LuaError::OK) << S; | ||
125 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:send(0, l:deep(), 'v')"), LuaError::OK) << S; | ||
126 | } | ||
127 | |||
128 | // ################################################################################################# | ||
129 | |||
130 | TEST_F(OneKeeperLindaTests, LindaSendSucceedsOnDeepUserdataKey) | ||
131 | { | ||
132 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:send(0, l, 'v')"), LuaError::OK) << S; | ||
133 | } | ||
134 | |||
135 | // ################################################################################################# | ||
136 | |||
137 | TEST_F(OneKeeperLindaTests, LindaSendSelfValidation) | ||
138 | { | ||
139 | // misuse checks, . instead of : | ||
140 | EXPECT_NE(S.doString("lanes.linda().send(nil, 'k', 'v')"), LuaError::OK); | ||
141 | } | ||
142 | |||
143 | // ################################################################################################# | ||
144 | |||
145 | TEST_F(OneKeeperLindaTests, LindaSendValueValidation) | ||
146 | { | ||
147 | // value checks | ||
148 | // linda:send() should fail if we don't send anything | ||
149 | EXPECT_NE(S.doString("lanes.linda():send()"), LuaError::OK); | ||
150 | EXPECT_NE(S.doString("lanes.linda():send(0)"), LuaError::OK); | ||
151 | EXPECT_NE(S.doString("lanes.linda():send(0, 'k')"), LuaError::OK); | ||
152 | // or non-deep userdata | ||
153 | EXPECT_NE(S.doString("lanes.linda():send(0, 'k', fixture.newuserdata())"), LuaError::OK); | ||
154 | // or something with a converter that raises an error (maybe that should go to a dedicated __lanesconvert test!) | ||
155 | EXPECT_NE(S.doString("lanes.linda():send(0, 'k', setmetatable({}, {__lanesconvert = function(where_) error (where_ .. ': should not send me' end}))"), LuaError::OK); | ||
156 | // but a registered non-deep userdata should work | ||
157 | EXPECT_EQ(S.doString("lanes.linda():send(0, 'k', io.stdin)"), LuaError::OK); | ||
158 | } | ||
159 | |||
160 | // ################################################################################################# | ||
161 | |||
162 | TEST_F(OneKeeperLindaTests, LindaLimitArgumentValidation) | ||
163 | { | ||
164 | // misuse checks, . instead of : | ||
165 | EXPECT_NE(S.doString("lanes.linda().limit()"), LuaError::OK); | ||
166 | |||
167 | // not enough keys | ||
168 | EXPECT_NE(S.doString("lanes.linda():limit()"), LuaError::OK); | ||
169 | |||
170 | // too many keys? | ||
171 | EXPECT_NE(S.doString("lanes.linda():limit('k1', 'k2')"), LuaError::OK); | ||
172 | EXPECT_NE(S.doString("lanes.linda():limit('k1', 'k2', 'k3')"), LuaError::OK); | ||
173 | |||
174 | // non-numeric limit | ||
175 | EXPECT_NE(S.doString("lanes.linda():limit('k', false)"), LuaError::OK); | ||
176 | EXPECT_NE(S.doString("lanes.linda():limit('k', true)"), LuaError::OK); | ||
177 | EXPECT_NE(S.doString("lanes.linda():limit('k', {})"), LuaError::OK); | ||
178 | EXPECT_NE(S.doString("lanes.linda():limit('k', lanes.linda():deep())"), LuaError::OK); | ||
179 | EXPECT_NE(S.doString("lanes.linda():limit('k', assert)"), LuaError::OK); | ||
180 | EXPECT_NE(S.doString("lanes.linda():limit('k', function() end)"), LuaError::OK); | ||
181 | |||
182 | // negative limit is forbidden | ||
183 | EXPECT_NE(S.doString("lanes.linda():limit('k', -1)"), LuaError::OK); | ||
184 | |||
185 | // we can set a positive limit, or "unlimited" | ||
186 | EXPECT_EQ(S.doString("lanes.linda():limit('k', 0)"), LuaError::OK) << S; | ||
187 | EXPECT_EQ(S.doString("lanes.linda():limit('k', 1)"), LuaError::OK) << S; | ||
188 | EXPECT_EQ(S.doString("lanes.linda():limit('k', 45648946)"), LuaError::OK) << S; | ||
189 | EXPECT_EQ(S.doString("lanes.linda():limit('k', 'unlimited')"), LuaError::OK) << S; | ||
190 | } | ||
191 | |||
192 | // ################################################################################################# | ||
193 | |||
194 | TEST_F(OneKeeperLindaTests, LindaCollectGarbage) | ||
195 | { | ||
196 | // linda:collectgarbage() doesn't accept extra arguments | ||
197 | EXPECT_NE(S.doString("lanes.linda():collectgarbage(true)"), LuaError::OK) << S; | ||
198 | EXPECT_EQ(S.doString("lanes.linda():collectgarbage()"), LuaError::OK) << S; | ||
199 | } | ||
200 | |||
201 | // ################################################################################################# | ||
202 | |||
203 | TEST_F(OneKeeperLindaTests, LindaCount) | ||
204 | { | ||
205 | // counting a non-existent key returns nothing | ||
206 | EXPECT_EQ(S.doString("assert(lanes.linda():count('k') == nil)"), LuaError::OK) << S; | ||
207 | // counting an existing key returns a correct count | ||
208 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:set('k', 'a'); assert(l:count('k') == 1)"), LuaError::OK) << S; | ||
209 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:set('k', 'a', 'b'); assert(l:count('k') == 2)"), LuaError::OK) << S; | ||
210 | } | ||
211 | |||
212 | // ################################################################################################# | ||
213 | |||
214 | TEST_F(OneKeeperLindaTests, LindaLimit) | ||
215 | { | ||
216 | // we can set an inexistent key to unlimited, it should do nothing | ||
217 | EXPECT_EQ(S.doString("local r,s = lanes.linda():limit('k', 'unlimited'); assert(r==false and s=='under')"), LuaError::OK) << S; | ||
218 | // reading the limit of an unset key should succeed | ||
219 | EXPECT_EQ(S.doString("local r,s = lanes.linda():limit('k'); assert(r=='unlimited' and s=='under')"), LuaError::OK) << S; | ||
220 | // reading the limit after we set one should yield the correct value | ||
221 | EXPECT_EQ(S.doString("local l = lanes.linda(); local r,s = l:limit('k', 3); assert(r==false and s=='under'); r,s = l:limit('k'); assert(r==3 and s=='under')"), LuaError::OK) << S; | ||
222 | // changing the limit is possible... | ||
223 | EXPECT_EQ(S.doString("local l = lanes.linda(); local r,s = l:limit('k', 3); r,s = l:limit('k', 5); r,s = l:limit('k'); assert(r==5 and s=='under', 'b')"), LuaError::OK) << S; | ||
224 | // ... even if we set a limit below the current count of stored data (which should not change) | ||
225 | EXPECT_EQ(S.doString("local l = lanes.linda(); local r,s = l:set('k', 'a', 'b', 'c'); assert(r==false and s=='under'); r,s = l:limit('k', 1); assert(r==false and s=='over' and l:count('k') == 3); r,s = l:limit('k'); assert(r==1 and s=='over')"), LuaError::OK) << S; | ||
226 | // we can remove the limit on a key | ||
227 | EXPECT_EQ(S.doString("lanes.linda():limit('k', 'unlimited')"), LuaError::OK) << S; | ||
228 | |||
229 | // emptying a limited key should not remove the limit | ||
230 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:limit('k', 5); l:set('k'); assert(l:limit('k')==5)"), LuaError::OK) << S; | ||
231 | } | ||
232 | |||
233 | // ################################################################################################# | ||
234 | |||
235 | TEST_F(OneKeeperLindaTests, LindaRestrict) | ||
236 | { | ||
237 | // we can read the access restriction of an inexistent Linda, it should tell us there is no restriction | ||
238 | EXPECT_EQ(S.doString("local r = lanes.linda():restrict('k'); assert(r=='none')"), LuaError::OK) << S; | ||
239 | // setting an unknown access restriction should fail | ||
240 | EXPECT_NE(S.doString("lanes.linda():restrict('k', 'gleh')"), LuaError::OK) << S; | ||
241 | // we can set the access restriction of an inexistent Linda, it should store it and return the previous restriction | ||
242 | EXPECT_EQ(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'set/get'); local r2 = l:restrict('k'); assert(r1=='none' and r2 == 'set/get')"), LuaError::OK) << S; | ||
243 | EXPECT_EQ(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'send/receive'); local r2 = l:restrict('k'); assert(r1=='none' and r2 == 'send/receive')"), LuaError::OK) << S; | ||
244 | |||
245 | // we can replace the restriction on a restricted linda | ||
246 | EXPECT_EQ(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'send/receive'); local r2 = l:restrict('k', 'set/get'); assert(r1=='none' and r2 == 'send/receive')"), LuaError::OK) << S; | ||
247 | |||
248 | // we can remove the restriction on a restricted linda | ||
249 | EXPECT_EQ(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'send/receive'); local r2 = l:restrict('k', 'none'); local r3 = l:restrict('k'); assert(r1=='none' and r2 == 'send/receive' and r3 == 'none')"), LuaError::OK) << S; | ||
250 | |||
251 | // can't use send/receive on a 'set/get'-restricted key | ||
252 | EXPECT_NE(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'set/get'); l:send('k', 'bob')"), LuaError::OK) << S; | ||
253 | EXPECT_NE(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'set/get'); l:receive('k')"), LuaError::OK) << S; | ||
254 | // can't use get/set on a 'send/receive'-restricted key | ||
255 | EXPECT_NE(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'send/receive'); l:set('k', 'bob')"), LuaError::OK) << S; | ||
256 | EXPECT_NE(S.doString("local l = lanes.linda(); local r1 = l:restrict('k', 'send/receive'); l:get('k')"), LuaError::OK) << S; | ||
257 | |||
258 | // emptying a restricted key should not cause the restriction to be forgotten | ||
259 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:restrict('k', 'set/get'); l:set('k'); assert(l:restrict('k')=='set/get')"), LuaError::OK) << S; | ||
260 | } | ||
261 | |||
262 | // ################################################################################################# | ||
263 | |||
264 | TEST_F(OneKeeperLindaTests, LindaSet) | ||
265 | { | ||
266 | // we can store more data than the specified limit | ||
267 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:limit('k', 1); local r,s = l:set('k', 'a', 'b', 'c'); assert(r == false and s == 'over'); assert(l:count('k') == 3)"), LuaError::OK) << S; | ||
268 | // setting nothing in an inexistent key does not create it | ||
269 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:set('k'); assert(l:count('k') == nil)"), LuaError::OK) << S; | ||
270 | // setting a key with some values yields the correct count | ||
271 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:set('k', 'a'); assert(l:count('k') == 1) "), LuaError::OK) << S; | ||
272 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:limit('k', 1); local r,s = l:set('k', 'a'); assert(r == false and s == 'exact'); assert(l:count('k') == 1)"), LuaError::OK) << S; | ||
273 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:set('k', 'a', 'b', 'c', 'd'); assert(l:count('k') == 4) "), LuaError::OK) << S; | ||
274 | // setting nothing in an existing key removes it ... | ||
275 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:set('k', 'a'); assert(l:count('k') == 1); l:set('k'); assert(l:count('k') == nil) "), LuaError::OK) << S; | ||
276 | // ... but not if there is a limit (because we don't want to forget it) | ||
277 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:limit('k', 1); l:set('k', 'a'); l:set('k'); assert(l:count('k') == 0) "), LuaError::OK) << S; | ||
278 | } | ||
279 | |||
280 | // ################################################################################################# | ||
281 | |||
282 | TEST_F(OneKeeperLindaTests, LindaCancel) | ||
283 | { | ||
284 | // unknown linda cancellation mode should raise an error | ||
285 | EXPECT_NE(S.doString("local l = lanes.linda(); l:cancel('zbougli');"), LuaError::OK) << S; | ||
286 | // cancelling a linda should change its cancel status to 'cancelled' | ||
287 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:cancel('read'); assert(l.status == 'cancelled')"), LuaError::OK) << S; | ||
288 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:cancel('write'); assert(l.status == 'cancelled')"), LuaError::OK) << S; | ||
289 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:cancel('both'); assert(l.status == 'cancelled')"), LuaError::OK) << S; | ||
290 | // resetting the linda cancel status | ||
291 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:cancel('none'); assert(l.status == 'active')"), LuaError::OK) << S; | ||
292 | } | ||
293 | |||
294 | // ################################################################################################# | ||
295 | |||
296 | TEST_F(OneKeeperLindaTests, LindaWake) | ||
297 | { | ||
298 | // unknown linda wake mode should raise an error | ||
299 | EXPECT_NE(S.doString("local l = lanes.linda(); l:wake('boulgza');"), LuaError::OK) << S; | ||
300 | // waking a linda should not change its cancel status | ||
301 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:wake('read'); assert(l.status == 'active')"), LuaError::OK) << S; | ||
302 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:wake('write'); assert(l.status == 'active')"), LuaError::OK) << S; | ||
303 | EXPECT_EQ(S.doString("local l = lanes.linda(); l:wake('both'); assert(l.status == 'active')"), LuaError::OK) << S; | ||
304 | } | ||
305 | |||
306 | // ################################################################################################# | ||
307 | // ################################################################################################# | ||
308 | |||
309 | class MultiKeeperLindaTests : public ::testing::Test | ||
310 | { | ||
311 | protected: | ||
312 | LuaState S{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
313 | |||
314 | void SetUp() override | ||
315 | { | ||
316 | std::ignore = S.doString("lanes = require 'lanes'.configure{nb_user_keepers = 3}"); | ||
317 | } | ||
318 | }; | ||
319 | |||
320 | TEST_F(MultiKeeperLindaTests, LindaCreation) | ||
321 | { | ||
322 | EXPECT_NE(S.doString("lanes.linda(-1)"), LuaError::OK); | ||
323 | EXPECT_EQ(S.doString("lanes.linda(0)"), LuaError::OK) << S; | ||
324 | EXPECT_EQ(S.doString("lanes.linda(1)"), LuaError::OK) << S; | ||
325 | EXPECT_EQ(S.doString("lanes.linda(2)"), LuaError::OK) << S; | ||
326 | EXPECT_EQ(S.doString("lanes.linda(3)"), LuaError::OK) << S; | ||
327 | EXPECT_NE(S.doString("lanes.linda(4)"), LuaError::OK); | ||
328 | } | ||
329 | |||
330 | // ################################################################################################# | ||
331 | // ################################################################################################# | ||
332 | |||
333 | INSTANTIATE_TEST_CASE_P( | ||
334 | LindaScriptedTests, | ||
335 | UnitTestRunner, | ||
336 | ::testing::Values( | ||
337 | FileRunnerParam{ "linda/send_receive", TestType::AssertNoLuaError }, | ||
338 | FileRunnerParam{ "linda/send_registered_userdata", TestType::AssertNoLuaError }, | ||
339 | FileRunnerParam{ "linda/multiple_keepers", TestType::AssertNoLuaError } | ||
340 | ) | ||
341 | //,[](::testing::TestParamInfo<FileRunnerParam> const& info_) { return std::string{ info_.param.script }; } | ||
342 | ); | ||
diff --git a/unit_tests/scripts/_assert.lua b/unit_tests/scripts/_assert.lua new file mode 100644 index 0000000..9bc8d32 --- /dev/null +++ b/unit_tests/scripts/_assert.lua | |||
@@ -0,0 +1,321 @@ | |||
1 | -- | ||
2 | -- ASSERT.LUA Copyright (c) 2006-07, <akauppi@gmail.com> | ||
3 | -- | ||
4 | -- Converting the Lua 'assert' function into a namespace table (without | ||
5 | -- breaking compatibility with the basic 'assert()' calling). | ||
6 | -- | ||
7 | -- This module allows shorthand use s.a. 'assert.table()' for asserting | ||
8 | -- variable types, and is also being used by Lua-super constraints system | ||
9 | -- for testing function argument & return types. | ||
10 | -- | ||
11 | -- All in all, a worthy module and could be part of Lua future versions. | ||
12 | -- | ||
13 | -- Note: the 'assert' table is available for your own assertions, too. Just add | ||
14 | -- more functions s.a. 'assert.myobj()' to check for custom invariants. | ||
15 | -- They will then be available for the constraints check, too. | ||
16 | -- | ||
17 | -- Author: <akauppi@gmail.com> | ||
18 | -- | ||
19 | --[[ | ||
20 | /****************************************************************************** | ||
21 | * Lua 5.1.1 support and extension functions (assert.lua) | ||
22 | * | ||
23 | * Copyright (C) 2006-07, Asko Kauppi. | ||
24 | * | ||
25 | * NOTE: This license concerns only the particular source file; not necessarily | ||
26 | * the project with which it has been delivered (the project may have a more | ||
27 | * restrictive license, s.a. [L]GPL). | ||
28 | * | ||
29 | * Permission is hereby granted, free of charge, to any person obtaining | ||
30 | * a copy of this software and associated documentation files (the | ||
31 | * "Software"), to deal in the Software without restriction, including | ||
32 | * without limitation the rights to use, copy, modify, merge, publish, | ||
33 | * distribute, sublicense, and/or sell copies of the Software, and to | ||
34 | * permit persons to whom the Software is furnished to do so, subject to | ||
35 | * the following conditions: | ||
36 | * | ||
37 | * The above copyright notice and this permission notice shall be | ||
38 | * included in all copies or substantial portions of the Software. | ||
39 | * | ||
40 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
41 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
42 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
43 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
44 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
45 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
46 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
47 | ******************************************************************************/ | ||
48 | ]]-- | ||
49 | |||
50 | local m= { _info= { MODULE= "Assert.* functions for constraints, and unit testing", | ||
51 | AUTHOR= "akauppi@gmail.com", | ||
52 | VERSION= 20240702, -- last change (yyyymmdd) | ||
53 | LICENSE= "MIT/X11" } } | ||
54 | |||
55 | -- Global changes: | ||
56 | -- 'assert' redefined, in a backwards compatible way | ||
57 | -- | ||
58 | -- Module functions: | ||
59 | -- none | ||
60 | |||
61 | assert( type(assert) == "function" ) -- system assert function | ||
62 | assert( type(error) == "function") | ||
63 | |||
64 | ----- | ||
65 | -- Integer range: INT_MIN..INT_MAX | ||
66 | -- | ||
67 | local function try_maxint( n ) | ||
68 | return (n > n-1) and n-1 -- false when outside the integer range | ||
69 | end | ||
70 | |||
71 | local INT_MAX= | ||
72 | try_maxint( 2^64 ) or | ||
73 | try_maxint( 2^32 ) or | ||
74 | try_maxint( 2^24 ) or -- float (24-bit mantissa) | ||
75 | assert( false ) | ||
76 | |||
77 | local INT_MIN= -(INT_MAX+1) | ||
78 | |||
79 | |||
80 | ---=== assert.*() ===--- | ||
81 | |||
82 | local at_msg= "type assertion error" -- TBD: better messages, about that exact situation | ||
83 | local av_msg= "value assertion error" | ||
84 | |||
85 | -- void= _assert( val [, msg_str [, lev_uint]] ) | ||
86 | -- | ||
87 | local function _assert( cond, msg, lev ) | ||
88 | -- original 'assert' provides no level override, so we use 'error'. | ||
89 | -- | ||
90 | if not cond then | ||
91 | error( msg or "assertion failed!", (lev or 1)+1 ) | ||
92 | end | ||
93 | end | ||
94 | |||
95 | -- Note: following code uses the _new_ 'assert()' by purpose, since it provides | ||
96 | -- a level override (original doesn't) | ||
97 | -- | ||
98 | local function assert_v( v0 ) | ||
99 | return function(v,msg) | ||
100 | _assert( v == v0, msg or av_msg, 2 ) | ||
101 | return v | ||
102 | end | ||
103 | end | ||
104 | local function assert_t( str ) | ||
105 | return function(v,msg) | ||
106 | _assert( type(v) == str, msg or at_msg, 2 ) | ||
107 | return v | ||
108 | end | ||
109 | end | ||
110 | local function assert_t2( str ) | ||
111 | return function(v,subtype,msg) | ||
112 | local t,st= type(v) | ||
113 | _assert( t==str and ((not subtype) or (st==subtype)), | ||
114 | msg or at_msg, 2 ) | ||
115 | return v | ||
116 | end | ||
117 | end | ||
118 | |||
119 | -- global base function assert() is replaced by this table | ||
120 | assert= | ||
121 | { | ||
122 | __metatable = "assert()", | ||
123 | __call= function(_,v,msg) -- plain 'assert()' (compatibility) | ||
124 | if v then return v end | ||
125 | _assert( v, msg, 2 ) | ||
126 | end, | ||
127 | |||
128 | -- Hopefully, Lua will allow use of 'nil', 'function' and other reserved words as table | ||
129 | -- shortcuts in the future (5.1.1 does not). | ||
130 | -- | ||
131 | ["nil"]= assert_v( nil ), | ||
132 | boolean= assert_t "boolean", | ||
133 | table= assert_t2 "table", | ||
134 | ["function"]= assert_t "function", | ||
135 | userdata= assert_t2 "userdata", | ||
136 | |||
137 | string= function( v, msg ) | ||
138 | local s= tostring(v) | ||
139 | _assert( s, msg or at_msg, 2 ) | ||
140 | return v | ||
141 | end, | ||
142 | |||
143 | char= function( v, msg ) | ||
144 | -- 'char' is _not_ doing int->string conversion | ||
145 | _assert( type(v)=="string" and v:len()==1, msg or at_msg, 2 ) | ||
146 | return v | ||
147 | end, | ||
148 | |||
149 | number= function( v, msg ) | ||
150 | _assert( tonumber(v), msg or at_msg, 2 ) | ||
151 | return v | ||
152 | end, | ||
153 | |||
154 | int= function( v, msg ) | ||
155 | local n= tonumber(v) | ||
156 | _assert( n and (n >= INT_MIN) and (n <= INT_MAX) and math.floor(n) == n, | ||
157 | msg or at_msg, 2 ) | ||
158 | return v | ||
159 | end, | ||
160 | |||
161 | uint= function( v, msg ) | ||
162 | local n= tonumber(v) | ||
163 | -- unsigned integer upper range is the same as integers' (there's no | ||
164 | -- real unsigned within the Lua) | ||
165 | _assert( n and (n >= 0) and (n <= INT_MAX) and math.floor(n) == n, | ||
166 | msg or at_msg, 2 ) | ||
167 | return v | ||
168 | end, | ||
169 | |||
170 | ['true']= assert_v( true ), | ||
171 | ['false']= assert_v( false ), | ||
172 | |||
173 | string_or_table= function( v, msg ) | ||
174 | assert( tostring(v) or type(v)=="table", msg or at_msg, 2 ) | ||
175 | return v | ||
176 | end, | ||
177 | |||
178 | number_or_string= function( v, msg ) | ||
179 | assert( tonumber(v) or type(v)=="table", msg or at_msg, 2 ) | ||
180 | return v | ||
181 | end, | ||
182 | |||
183 | any= function( v, msg ) | ||
184 | assert( v ~= nil, msg or av_msg, 2 ) | ||
185 | return v | ||
186 | end, | ||
187 | |||
188 | -- Range assertion, with extra arguments per instance | ||
189 | -- | ||
190 | -- Note: values may be of _any_ type that can do >=, <= comparisons. | ||
191 | -- | ||
192 | range= function( lo, hi ) | ||
193 | _assert( lo and hi and lo <= hi, "Bad limits", 2 ) | ||
194 | -- make sure the limits make sense (just once) | ||
195 | |||
196 | return function(v,msg,lev) | ||
197 | if ( (lo and v<lo) or (hi and v>hi) ) then | ||
198 | msg= msg or "not in range: ("..(lo or "")..","..(hi or "")..")" | ||
199 | _assert( false, msg, 2 ) | ||
200 | end | ||
201 | return v | ||
202 | end | ||
203 | end, | ||
204 | |||
205 | -- Table contents assertion | ||
206 | -- - no unknown (non-numeric) keys are allowed | ||
207 | -- - numeric keys are ignored | ||
208 | -- | ||
209 | -- Constraints patch should point to this, when using the ":{ ... }" constraint. | ||
210 | -- | ||
211 | ["{}"]= function( tbl ) | ||
212 | |||
213 | -- check all keys in 't' (including numeric, if any) that they do exist, | ||
214 | -- and carry the right type | ||
215 | -- | ||
216 | local function subf1(v,t,msg,lev) | ||
217 | _assert(lev) | ||
218 | for k,f in pairs(t) do | ||
219 | -- 'f' is an assert function, or subtable | ||
220 | local ft= type(f) | ||
221 | if ft=="function" then | ||
222 | f( v[k], msg, lev+1 ) | ||
223 | elseif ft=="table" then | ||
224 | _assert( type(v[k])=="table", msg or "no subtable "..tostring(k), lev+1 ) | ||
225 | subf1( v[k], f, msg, lev+1 ) | ||
226 | else | ||
227 | error( "Bad constraints table for '"..tostring(k).."'! (not a function)", lev+1 ) | ||
228 | end | ||
229 | end | ||
230 | end | ||
231 | |||
232 | -- check there are no other (non-numeric) keys in 'v' | ||
233 | local function subf2(v,t,msg,lev) | ||
234 | _assert(lev) | ||
235 | for k,vv in pairs(v) do | ||
236 | if type(k)=="number" then | ||
237 | -- skip them | ||
238 | elseif not t[k] then | ||
239 | _assert( false, msg or "extra field: '"..tostring(k).."'", lev+1 ) | ||
240 | elseif type(vv)=="table" then | ||
241 | subf2( vv, t[k], msg, lev+1 ) | ||
242 | end | ||
243 | end | ||
244 | end | ||
245 | |||
246 | _assert( type(tbl)=="table", "Wrong argument to assert['{}']" ) | ||
247 | |||
248 | return function( v, msg, lev ) | ||
249 | lev= (lev or 1)+1 | ||
250 | _assert( type(v)=="table" ,msg, lev ) | ||
251 | subf1( v, tbl, msg, lev ) | ||
252 | subf2( v, tbl, msg, lev ) | ||
253 | return v | ||
254 | end | ||
255 | end, | ||
256 | |||
257 | -- ... | ||
258 | } | ||
259 | setmetatable( assert, assert ) | ||
260 | |||
261 | assert.void= assert["nil"] | ||
262 | |||
263 | |||
264 | ----- | ||
265 | -- void= assert.fails( function [,err_msg_str] ) | ||
266 | -- | ||
267 | -- Special assert function, to make sure the call within it fails, and gives a | ||
268 | -- specific error message (to be used in unit testing). | ||
269 | -- | ||
270 | function assert.fails( func_block, err_msg ) | ||
271 | -- | ||
272 | local st,err= pcall( func_block ) | ||
273 | if st then | ||
274 | _assert( false, "Block expected to fail, but didn't.", 2 ) | ||
275 | elseif err_msg and err ~= err_msg then | ||
276 | _assert( false, "Failed with wrong error message: \n".. | ||
277 | "'"..err.."'\nexpected: '"..err_msg.."'", 2 ) | ||
278 | end | ||
279 | end | ||
280 | |||
281 | |||
282 | ----- | ||
283 | -- void= assert.failsnot( function [,err_msg_str] ) | ||
284 | -- | ||
285 | -- Similar to 'assert.fails' but expects the code to survive. | ||
286 | -- | ||
287 | function assert.failsnot( func_block, err_msg ) | ||
288 | -- | ||
289 | local st,err= pcall( func_block ) | ||
290 | if not st then | ||
291 | _assert( false, "Block expected NOT to fail, but did.".. | ||
292 | (err and "\n\tError: '"..err.."'" or ""), 2 ) | ||
293 | end | ||
294 | end | ||
295 | |||
296 | |||
297 | ----- | ||
298 | -- void= assert.nilerr( function [,err_msg_str] ) | ||
299 | -- | ||
300 | -- Expects the function to return with 'nil,err' failure code, with | ||
301 | -- optionally error string matching. Similar to --> 'assert.fails()' | ||
302 | -- | ||
303 | function assert.nilerr( func_block, err_msg ) | ||
304 | -- | ||
305 | local v,err= func_block() | ||
306 | _assert( v==nil, "Expected to return nil, but didn't: "..tostring(v), 2 ) | ||
307 | if err_msg and err ~= err_msg then | ||
308 | _assert( false, "Failed with wrong error message: \n".. | ||
309 | "'"..err.."'\nexpected: '"..err_msg.."'", 2 ) | ||
310 | end | ||
311 | end | ||
312 | |||
313 | |||
314 | -- Sanity check | ||
315 | -- | ||
316 | assert( true ) | ||
317 | assert.fails( function() assert( false ) end ) | ||
318 | assert.fails( function() assert( nil ) end ) | ||
319 | |||
320 | |||
321 | return m -- just info | ||
diff --git a/unit_tests/scripts/_utils.lua b/unit_tests/scripts/_utils.lua new file mode 100644 index 0000000..d710702 --- /dev/null +++ b/unit_tests/scripts/_utils.lua | |||
@@ -0,0 +1,75 @@ | |||
1 | local io = assert(io) | ||
2 | local pairs = assert(pairs) | ||
3 | local select = assert(select) | ||
4 | local string_format = assert(string.format) | ||
5 | local tostring = assert(tostring) | ||
6 | local type = assert(type) | ||
7 | |||
8 | local print_id = 0 | ||
9 | local P = function(whence_, ...) | ||
10 | if not io then return end | ||
11 | print_id = print_id + 1 | ||
12 | local _str = string_format("%s: %02d.\t", whence_, print_id) | ||
13 | for _i = 1, select('#', ...) do | ||
14 | _str = _str .. tostring(select(_i, ...)) .. "\t" | ||
15 | end | ||
16 | if io then | ||
17 | io.stderr:write(_str .. "\n") | ||
18 | end | ||
19 | end | ||
20 | |||
21 | local MAKE_PRINT = function() | ||
22 | local _whence = lane_threadname and lane_threadname() or "main" | ||
23 | return function(...) | ||
24 | P(_whence, ...) | ||
25 | end | ||
26 | end | ||
27 | |||
28 | local tables_match | ||
29 | |||
30 | -- true if 'a' is a subtable of 'b' | ||
31 | -- | ||
32 | local function subtable(a, b) | ||
33 | -- | ||
34 | assert(type(a)=="table" and type(b)=="table") | ||
35 | |||
36 | for k,v in pairs(b) do | ||
37 | if type(v)~=type(a[k]) then | ||
38 | return false -- not subtable (different types, or missing key) | ||
39 | elseif type(v)=="table" then | ||
40 | if not tables_match(v,a[k]) then return false end | ||
41 | else | ||
42 | if a[k] ~= v then return false end | ||
43 | end | ||
44 | end | ||
45 | return true -- is a subtable | ||
46 | end | ||
47 | |||
48 | -- true when contents of 'a' and 'b' are identical | ||
49 | -- | ||
50 | tables_match = function(a, b) | ||
51 | return subtable(a, b) and subtable(b, a) | ||
52 | end | ||
53 | |||
54 | local function dump_error_stack(error_reporting_mode_, stack) | ||
55 | local PRINT = MAKE_PRINT() | ||
56 | if error_reporting_mode_ == "minimal" then | ||
57 | assert(stack == nil, table.concat{"stack is a ", type(stack)}) | ||
58 | elseif error_reporting_mode_ == "basic" then -- each stack line is a string in basic mode | ||
59 | PRINT("STACK:\n", table.concat(stack,"\n\t")); | ||
60 | else -- each stack line is a table in extended mode | ||
61 | PRINT "STACK:" | ||
62 | for line, details in pairs(stack) do | ||
63 | PRINT("\t", line); | ||
64 | for detail, value in pairs(details) do | ||
65 | PRINT("\t\t", detail, ": ", value) | ||
66 | end | ||
67 | end | ||
68 | end | ||
69 | end | ||
70 | |||
71 | return { | ||
72 | MAKE_PRINT = MAKE_PRINT, | ||
73 | tables_match = tables_match, | ||
74 | dump_error_stack = dump_error_stack | ||
75 | } | ||
diff --git a/unit_tests/scripts/coro/basics.lua b/unit_tests/scripts/coro/basics.lua new file mode 100644 index 0000000..4444e87 --- /dev/null +++ b/unit_tests/scripts/coro/basics.lua | |||
@@ -0,0 +1,94 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | local fixture = require "fixture" | ||
4 | lanes.finally(fixture.throwing_finalizer) | ||
5 | |||
6 | local utils = lanes.require "_utils" | ||
7 | local PRINT = utils.MAKE_PRINT() | ||
8 | |||
9 | if true then | ||
10 | -- a lane body that just returns some value | ||
11 | local lane = function(msg_) | ||
12 | local utils = lanes.require "_utils" | ||
13 | local PRINT = utils.MAKE_PRINT() | ||
14 | PRINT "In lane" | ||
15 | assert(msg_ == "hi") | ||
16 | return "bye" | ||
17 | end | ||
18 | |||
19 | -- the generator | ||
20 | local g1 = lanes.coro("*", {name = "auto"}, lane) | ||
21 | |||
22 | -- launch lane | ||
23 | local h1 = g1("hi") | ||
24 | |||
25 | local r = h1[1] | ||
26 | assert(r == "bye") | ||
27 | end | ||
28 | |||
29 | -- a lane coroutine that yields back what got in, one element at a time | ||
30 | local yielder = function(...) | ||
31 | local utils = lanes.require "_utils" | ||
32 | local PRINT = utils.MAKE_PRINT() | ||
33 | PRINT "In lane" | ||
34 | for _i = 1, select('#', ...) do | ||
35 | local _val = select(_i, ...) | ||
36 | PRINT("yielding #", _i, _val) | ||
37 | local _ack = coroutine.yield(_val) | ||
38 | assert(_ack == _i) | ||
39 | end | ||
40 | return "done!" | ||
41 | end | ||
42 | |||
43 | if true then | ||
44 | -- if we start a non-coroutine lane with a yielding function, we should get an error, right? | ||
45 | local fun_g = lanes.gen("*", {name = "auto"}, yielder) | ||
46 | local h = fun_g("hello", "world", "!") | ||
47 | local err, status, stack = h:join() | ||
48 | PRINT(err, status, stack) | ||
49 | -- the actual error message is not the same for Lua 5.1 | ||
50 | local msgs = { | ||
51 | ["Lua 5.1"] = "attempt to yield across metamethod/C-call boundary", | ||
52 | ["Lua 5.2"] = "attempt to yield from outside a coroutine", | ||
53 | ["Lua 5.3"] = "attempt to yield from outside a coroutine", | ||
54 | ["Lua 5.4"] = "attempt to yield from outside a coroutine" | ||
55 | } | ||
56 | local expected_msg = msgs[_VERSION] | ||
57 | assert(err == nil and status == expected_msg and stack == nil, "status = " .. status) | ||
58 | end | ||
59 | |||
60 | -- the generator | ||
61 | local coro_g = lanes.coro("*", {name = "auto"}, yielder) | ||
62 | |||
63 | if true then | ||
64 | -- launch coroutine lane | ||
65 | local h2 = coro_g("hello", "world", "!") | ||
66 | -- read the yielded values, sending back the expected index | ||
67 | assert(h2:resume(1) == "hello") | ||
68 | assert(h2:resume(2) == "world") | ||
69 | assert(h2:resume(3) == "!") | ||
70 | -- the lane return value is available as usual | ||
71 | local r = h2[1] | ||
72 | assert(r == "done!") | ||
73 | end | ||
74 | |||
75 | if true then | ||
76 | -- another coroutine lane | ||
77 | local h3 = coro_g("hello", "world", "!") | ||
78 | |||
79 | -- yielded values are available as regular return values | ||
80 | assert(h3[1] == "hello" and h3.status == "suspended") | ||
81 | -- since we consumed the returned values, they should not be here when we resume | ||
82 | assert(h3:resume(1) == nil) | ||
83 | |||
84 | -- similarly, we can get them with join() | ||
85 | assert(h3:join() == "world" and h3.status == "suspended") | ||
86 | -- since we consumed the returned values, they should not be here when we resume | ||
87 | assert(h3:resume(2) == nil) | ||
88 | |||
89 | -- the rest should work as usual | ||
90 | assert(h3:resume(3) == "!") | ||
91 | |||
92 | -- the final return value of the lane body remains to be read | ||
93 | assert(h3:join() == "done!" and h3.status == "done") | ||
94 | end | ||
diff --git a/unit_tests/scripts/coro/error_handling.lua b/unit_tests/scripts/coro/error_handling.lua new file mode 100644 index 0000000..3b50c7f --- /dev/null +++ b/unit_tests/scripts/coro/error_handling.lua | |||
@@ -0,0 +1,64 @@ | |||
1 | local lanes = require "lanes".configure{strip_functions = true} | ||
2 | |||
3 | local fixture = require "fixture" | ||
4 | lanes.finally(fixture.throwing_finalizer) | ||
5 | |||
6 | local utils = lanes.require "_utils" | ||
7 | local PRINT = utils.MAKE_PRINT() | ||
8 | |||
9 | -- a lane coroutine that yields back what got in, one element at a time | ||
10 | local yielder = function(...) | ||
11 | local utils = lanes.require "_utils" | ||
12 | local PRINT = utils.MAKE_PRINT() | ||
13 | PRINT "In lane" | ||
14 | for _i = 1, select('#', ...) do | ||
15 | local _val = select(_i, ...) | ||
16 | PRINT("yielding #", _i, _val) | ||
17 | local _ack = coroutine.yield(_val) | ||
18 | assert(_ack == _i, "unexpected reply ".._ack) | ||
19 | end | ||
20 | return "done!" | ||
21 | end | ||
22 | |||
23 | local force_error_test = function(error_trace_level_) | ||
24 | -- the generator, minimal error handling | ||
25 | local coro_g = lanes.coro("*", {name = "auto", error_trace_level = error_trace_level_}, yielder) | ||
26 | |||
27 | -- launch coroutine lane | ||
28 | local h = coro_g("hello", "world", "!") | ||
29 | -- read the yielded values, sending back the expected index | ||
30 | assert(h:resume(1) == "hello") | ||
31 | assert(h:resume(2) == "world") | ||
32 | -- mistake: we resume with 0 when the lane expects 3 -> assert() in the lane body! | ||
33 | assert(h:resume(0) == "!") | ||
34 | local a, b, c = h:join() | ||
35 | PRINT(error_trace_level_ .. ":", a, b, c) | ||
36 | local expected_c_type = error_trace_level_ == "minimal" and "nil" or "table" | ||
37 | assert(h.status == "error" and string.find(b, "unexpected reply 0", 1, true) and type(c) == expected_c_type, "error message is " .. b) | ||
38 | utils.dump_error_stack(error_trace_level_, c) | ||
39 | end | ||
40 | |||
41 | if false then | ||
42 | force_error_test("minimal") | ||
43 | end | ||
44 | |||
45 | if false then | ||
46 | force_error_test("basic") | ||
47 | end | ||
48 | |||
49 | if false then | ||
50 | force_error_test("extended") | ||
51 | end | ||
52 | |||
53 | if true then | ||
54 | -- start a coroutine lane that ends with a non-string error | ||
55 | local non_string_thrower = function() | ||
56 | error({"string in table"}) | ||
57 | end | ||
58 | local coro_g = lanes.coro("*", {name = "auto"}, non_string_thrower) | ||
59 | local h = coro_g() | ||
60 | local a, b, c = h:join() | ||
61 | -- we get the expected error back | ||
62 | PRINT("non_string_thrower:", a, b, c) | ||
63 | assert(a == nil and type(b) == "table" and b[1] == "string in table" and c == nil) | ||
64 | end | ||
diff --git a/unit_tests/scripts/lane/cooperative_shutdown.lua b/unit_tests/scripts/lane/cooperative_shutdown.lua new file mode 100644 index 0000000..2649079 --- /dev/null +++ b/unit_tests/scripts/lane/cooperative_shutdown.lua | |||
@@ -0,0 +1,45 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | -- launch lanes that cooperate properly with cancellation request | ||
4 | |||
5 | local lane1 = function() | ||
6 | -- loop breaks on cancellation request | ||
7 | repeat | ||
8 | lanes.sleep(0) | ||
9 | until cancel_test() | ||
10 | print "lane1 cancelled" | ||
11 | end | ||
12 | |||
13 | local lane2 = function(linda_) | ||
14 | -- loop breaks on lane/linda cancellation | ||
15 | repeat | ||
16 | local k, v = linda_:receive('k') | ||
17 | until v == lanes.cancel_error | ||
18 | print "lane2 cancelled" | ||
19 | end | ||
20 | |||
21 | local lane3 = function() | ||
22 | -- this one cooperates too, because of the hook cancellation modes that Lanes will be using | ||
23 | -- but not with LuaJIT, because the function is compiled, and we don't call anyone, so no hook triggers | ||
24 | repeat until false | ||
25 | end | ||
26 | |||
27 | |||
28 | |||
29 | -- the generators | ||
30 | local g1 = lanes.gen("*", lane1) | ||
31 | local g2 = lanes.gen("*", lane2) | ||
32 | local g3 = lanes.gen("*", lane3) | ||
33 | |||
34 | -- launch lanes | ||
35 | local h1 = g1() | ||
36 | |||
37 | local linda = lanes.linda() | ||
38 | local h2 = g2(linda) | ||
39 | |||
40 | local h3 = g3() | ||
41 | |||
42 | -- wait until they are both started | ||
43 | repeat until h1.status == "running" and h2.status == "waiting" and h3.status == "running" | ||
44 | |||
45 | -- let the script terminate, Lanes should not crash at shutdown | ||
diff --git a/unit_tests/scripts/lane/stdlib_naming.lua b/unit_tests/scripts/lane/stdlib_naming.lua new file mode 100644 index 0000000..2e045c3 --- /dev/null +++ b/unit_tests/scripts/lane/stdlib_naming.lua | |||
@@ -0,0 +1,87 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local lanes_gen = assert(lanes.gen) | ||
12 | local lanes_linda = assert(lanes.linda) | ||
13 | |||
14 | -- ################################################################################################## | ||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | |||
18 | local function task(a, b, c) | ||
19 | lane_threadname("task("..a..","..b..","..c..")") | ||
20 | --error "111" -- testing error messages | ||
21 | assert(hey) | ||
22 | local v=0 | ||
23 | for i=a,b,c do | ||
24 | v= v+i | ||
25 | end | ||
26 | return v, hey | ||
27 | end | ||
28 | |||
29 | local gc_cb = function(name_, status_) | ||
30 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
31 | end | ||
32 | |||
33 | -- ################################################################################################## | ||
34 | -- ################################################################################################## | ||
35 | -- ################################################################################################## | ||
36 | |||
37 | PRINT("\n\n", "---=== Stdlib naming ===---", "\n\n") | ||
38 | |||
39 | local function dump_g(_x) | ||
40 | lane_threadname "dump_g" | ||
41 | assert(print) | ||
42 | print("### dumping _G for '" .. _x .. "'") | ||
43 | for k, v in pairs(_G) do | ||
44 | print("\t" .. k .. ": " .. type(v)) | ||
45 | end | ||
46 | return true | ||
47 | end | ||
48 | |||
49 | local function io_os_f(_x) | ||
50 | lane_threadname "io_os_f" | ||
51 | assert(print) | ||
52 | print("### checking io and os libs existence for '" .. _x .. "'") | ||
53 | assert(io) | ||
54 | assert(os) | ||
55 | return true | ||
56 | end | ||
57 | |||
58 | local function coro_f(_x) | ||
59 | lane_threadname "coro_f" | ||
60 | assert(print) | ||
61 | print("### checking coroutine lib existence for '" .. _x .. "'") | ||
62 | assert(coroutine) | ||
63 | return true | ||
64 | end | ||
65 | |||
66 | assert.fails(function() lanes_gen("xxx", {gc_cb = gc_cb}, io_os_f) end) | ||
67 | |||
68 | local stdlib_naming_tests = | ||
69 | { | ||
70 | -- { "", dump_g}, | ||
71 | -- { "coroutine", dump_g}, | ||
72 | -- { "io", dump_g}, | ||
73 | -- { "bit32", dump_g}, | ||
74 | { "coroutine?", coro_f}, -- in Lua 5.1, the coroutine base library doesn't exist (coroutine table is created when 'base' is opened) | ||
75 | { "*", io_os_f}, | ||
76 | { "io,os", io_os_f}, | ||
77 | { "io+os", io_os_f}, | ||
78 | { "/io;os[base{", io_os_f}, -- use unconventional name separators to check that everything works fine | ||
79 | } | ||
80 | |||
81 | for _, t in ipairs(stdlib_naming_tests) do | ||
82 | local f= lanes_gen(t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do | ||
83 | assert(f(t[1])[1]) | ||
84 | end | ||
85 | |||
86 | PRINT("collectgarbage") | ||
87 | collectgarbage() | ||
diff --git a/unit_tests/scripts/lane/tasking_basic.lua b/unit_tests/scripts/lane/tasking_basic.lua new file mode 100644 index 0000000..99be321 --- /dev/null +++ b/unit_tests/scripts/lane/tasking_basic.lua | |||
@@ -0,0 +1,74 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local lanes_gen = assert(lanes.gen) | ||
12 | local lanes_linda = assert(lanes.linda) | ||
13 | |||
14 | -- ################################################################################################## | ||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | |||
18 | local function task(a, b, c) | ||
19 | local new_name = "task("..a..","..b..","..c..")" | ||
20 | -- test lane naming change | ||
21 | lane_threadname(new_name) | ||
22 | assert(lane_threadname() == new_name) | ||
23 | --error "111" -- testing error messages | ||
24 | assert(hey) | ||
25 | local v=0 | ||
26 | for i=a,b,c do | ||
27 | v= v+i | ||
28 | end | ||
29 | return v, hey | ||
30 | end | ||
31 | |||
32 | local gc_cb = function(name_, status_) | ||
33 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
34 | end | ||
35 | |||
36 | -- ################################################################################################## | ||
37 | -- ################################################################################################## | ||
38 | -- ################################################################################################## | ||
39 | |||
40 | PRINT("\n\n", "---=== Tasking (basic) ===---", "\n\n") | ||
41 | |||
42 | local task_launch = lanes_gen("", { globals={hey=true}, gc_cb = gc_cb}, task) | ||
43 | -- base stdlibs, normal priority | ||
44 | |||
45 | -- 'task_launch' is a factory of multithreaded tasks, we can launch several: | ||
46 | |||
47 | local lane1 = task_launch(100,200,3) | ||
48 | assert.fails(function() print(lane1[lane1]) end) -- indexing the lane with anything other than a string or a number should fail | ||
49 | lanes.sleep(0.1) -- give some time so that the lane can set its name | ||
50 | assert(lane1:get_threadname() == "task(100,200,3)", "Lane name is " .. lane1:get_threadname()) | ||
51 | |||
52 | local lane2= task_launch(200,300,4) | ||
53 | |||
54 | -- At this stage, states may be "pending", "running" or "done" | ||
55 | |||
56 | local st1,st2= lane1.status, lane2.status | ||
57 | PRINT(st1,st2) | ||
58 | assert(st1=="pending" or st1=="running" or st1=="done") | ||
59 | assert(st2=="pending" or st2=="running" or st2=="done") | ||
60 | |||
61 | -- Accessing results ([1..N]) pends until they are available | ||
62 | -- | ||
63 | PRINT("waiting...") | ||
64 | local v1, v1_hey= lane1[1], lane1[2] | ||
65 | local v2, v2_hey= lane2[1], lane2[2] | ||
66 | |||
67 | PRINT(v1, v1_hey) | ||
68 | assert(v1_hey == true) | ||
69 | |||
70 | PRINT(v2, v2_hey) | ||
71 | assert(v2_hey == true) | ||
72 | |||
73 | assert(lane1.status == "done") | ||
74 | assert(lane1.status == "done") | ||
diff --git a/unit_tests/scripts/lane/tasking_cancelling.lua b/unit_tests/scripts/lane/tasking_cancelling.lua new file mode 100644 index 0000000..a4e0fde --- /dev/null +++ b/unit_tests/scripts/lane/tasking_cancelling.lua | |||
@@ -0,0 +1,122 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local lanes_gen = assert(lanes.gen) | ||
12 | local lanes_linda = assert(lanes.linda) | ||
13 | |||
14 | -- ################################################################################################## | ||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | |||
18 | local function task(a, b, c) | ||
19 | lane_threadname("task("..a..","..b..","..c..")") | ||
20 | --error "111" -- testing error messages | ||
21 | assert(hey) | ||
22 | local v=0 | ||
23 | for i=a,b,c do | ||
24 | v= v+i | ||
25 | end | ||
26 | return v, hey | ||
27 | end | ||
28 | |||
29 | local gc_cb = function(name_, status_) | ||
30 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
31 | end | ||
32 | |||
33 | -- ################################################################################################## | ||
34 | -- ################################################################################################## | ||
35 | -- ################################################################################################## | ||
36 | |||
37 | PRINT("\n\n", "---=== Tasking (cancelling) ===---", "\n\n") | ||
38 | |||
39 | local task_launch2 = lanes_gen("", { globals={hey=true}, gc_cb = gc_cb}, task) | ||
40 | |||
41 | local N=999999999 | ||
42 | local lane9= task_launch2(1,N,1) -- huuuuuuge... | ||
43 | |||
44 | -- Wait until state changes "pending"->"running" | ||
45 | -- | ||
46 | local st | ||
47 | local t0= os.time() | ||
48 | while os.time()-t0 < 5 do | ||
49 | st= lane9.status | ||
50 | io.stderr:write((i==1) and st.." " or '.') | ||
51 | if st~="pending" then break end | ||
52 | end | ||
53 | PRINT(" "..st) | ||
54 | |||
55 | if st=="error" then | ||
56 | local _= lane9[0] -- propagate the error here | ||
57 | end | ||
58 | if st=="done" then | ||
59 | error("Looping to "..N.." was not long enough (cannot test cancellation)") | ||
60 | end | ||
61 | assert(st=="running", "st == " .. st) | ||
62 | |||
63 | -- when running under luajit, the function is JIT-ed, and the instruction count isn't hit, so we need a different hook | ||
64 | lane9:cancel(jit and "line" or "count", 100) -- 0 timeout, hook triggers cancelslation when reaching the specified count | ||
65 | |||
66 | local t0= os.time() | ||
67 | while os.time()-t0 < 5 do | ||
68 | st= lane9.status | ||
69 | io.stderr:write((i==1) and st.." " or '.') | ||
70 | if st~="running" then break end | ||
71 | end | ||
72 | PRINT(" "..st) | ||
73 | assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'") | ||
74 | |||
75 | -- cancellation of lanes waiting on a linda | ||
76 | local limited = lanes_linda("limited") | ||
77 | assert.fails(function() limited:limit("key", -1) end) | ||
78 | assert.failsnot(function() limited:limit("key", 1) end) | ||
79 | -- [[################################################ | ||
80 | limited:send("key", "hello") -- saturate linda | ||
81 | for k, v in pairs(limited:dump()) do | ||
82 | PRINT("limited[" .. tostring(k) .. "] = " .. tostring(v)) | ||
83 | end | ||
84 | local wait_send = function() | ||
85 | local a,b | ||
86 | set_finalizer(function() print("wait_send", a, b) end) | ||
87 | a,b = limited:send("key", "bybye") -- infinite timeout, returns only when lane is cancelled | ||
88 | end | ||
89 | |||
90 | local wait_send_lane = lanes.gen("*", wait_send)() | ||
91 | repeat until wait_send_lane.status == "waiting" | ||
92 | print "wait_send_lane is waiting" | ||
93 | wait_send_lane:cancel() -- hard cancel, 0 timeout | ||
94 | repeat until wait_send_lane.status == "cancelled" | ||
95 | print "wait_send_lane is cancelled" | ||
96 | --################################################]] | ||
97 | local wait_receive = function() | ||
98 | local k, v | ||
99 | set_finalizer(function() print("wait_receive", k, v) end) | ||
100 | k, v = limited:receive("dummy") -- infinite timeout, returns only when lane is cancelled | ||
101 | end | ||
102 | |||
103 | local wait_receive_lane = lanes.gen("*", wait_receive)() | ||
104 | repeat until wait_receive_lane.status == "waiting" | ||
105 | print "wait_receive_lane is waiting" | ||
106 | wait_receive_lane:cancel() -- hard cancel, 0 timeout | ||
107 | repeat until wait_receive_lane.status == "cancelled" | ||
108 | print "wait_receive_lane is cancelled" | ||
109 | --################################################]] | ||
110 | local wait_receive_batched = function() | ||
111 | local k, v1, v2 | ||
112 | set_finalizer(function() print("wait_receive_batched", k, v1, v2) end) | ||
113 | k, v1, v2 = limited:receive(limited.batched, "dummy", 2) -- infinite timeout, returns only when lane is cancelled | ||
114 | end | ||
115 | |||
116 | local wait_receive_batched_lane = lanes.gen("*", wait_receive_batched)() | ||
117 | repeat until wait_receive_batched_lane.status == "waiting" | ||
118 | print "wait_receive_batched_lane is waiting" | ||
119 | wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout | ||
120 | repeat until wait_receive_batched_lane.status == "cancelled" | ||
121 | print "wait_receive_batched_lane is cancelled" | ||
122 | --################################################]] | ||
diff --git a/unit_tests/scripts/lane/tasking_comms_criss_cross.lua b/unit_tests/scripts/lane/tasking_comms_criss_cross.lua new file mode 100644 index 0000000..db63b8e --- /dev/null +++ b/unit_tests/scripts/lane/tasking_comms_criss_cross.lua | |||
@@ -0,0 +1,53 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local lanes_gen = assert(lanes.gen) | ||
12 | local lanes_linda = assert(lanes.linda) | ||
13 | |||
14 | -- ################################################################################################## | ||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | |||
18 | local gc_cb = function(name_, status_) | ||
19 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
20 | end | ||
21 | |||
22 | -- ################################################################################################## | ||
23 | -- ################################################################################################## | ||
24 | -- ################################################################################################## | ||
25 | |||
26 | PRINT("\n\n", "---=== Comms criss cross ===---", "\n\n") | ||
27 | |||
28 | -- We make two identical lanes, which are using the same Linda channel. | ||
29 | -- | ||
30 | local tc = lanes_gen("io", {gc_cb = gc_cb}, | ||
31 | function(linda, ch_in, ch_out) | ||
32 | lane_threadname("criss cross " .. ch_in .. " -> " .. ch_out) | ||
33 | local function STAGE(str) | ||
34 | io.stderr:write(ch_in..": "..str.."\n") | ||
35 | linda:send(nil, ch_out, str) | ||
36 | local k,v= linda:receive(nil, ch_in) | ||
37 | assert(v==str) | ||
38 | end | ||
39 | STAGE("Hello") | ||
40 | STAGE("I was here first!") | ||
41 | STAGE("So what?") | ||
42 | end | ||
43 | ) | ||
44 | |||
45 | local linda= lanes_linda("criss cross") | ||
46 | |||
47 | local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms | ||
48 | |||
49 | local _= a[0],b[0] -- waits until they are both ready | ||
50 | |||
51 | PRINT("collectgarbage") | ||
52 | a, b = nil | ||
53 | collectgarbage() | ||
diff --git a/unit_tests/scripts/lane/tasking_communications.lua b/unit_tests/scripts/lane/tasking_communications.lua new file mode 100644 index 0000000..b922973 --- /dev/null +++ b/unit_tests/scripts/lane/tasking_communications.lua | |||
@@ -0,0 +1,149 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local lanes_gen = assert(lanes.gen) | ||
12 | local lanes_linda = assert(lanes.linda) | ||
13 | |||
14 | -- ################################################################################################## | ||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | |||
18 | local function task(a, b, c) | ||
19 | lane_threadname("task("..a..","..b..","..c..")") | ||
20 | --error "111" -- testing error messages | ||
21 | assert(hey) | ||
22 | local v=0 | ||
23 | for i=a,b,c do | ||
24 | v= v+i | ||
25 | end | ||
26 | return v, hey | ||
27 | end | ||
28 | |||
29 | local gc_cb = function(name_, status_) | ||
30 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
31 | end | ||
32 | |||
33 | -- ################################################################################################## | ||
34 | -- ################################################################################################## | ||
35 | -- ################################################################################################## | ||
36 | |||
37 | local tables_match = utils.tables_match | ||
38 | |||
39 | local SLEEP = function(...) | ||
40 | local k, v = lanes.sleep(...) | ||
41 | assert(k == nil and v == "timeout") | ||
42 | end | ||
43 | |||
44 | PRINT("\n\n", "---=== Communications ===---", "\n\n") | ||
45 | |||
46 | local function WR(...) io.stderr:write(...) end | ||
47 | |||
48 | local chunk= function(linda) | ||
49 | local function receive() return linda:receive("->") end | ||
50 | local function send(...) local _res, _err = linda:send("<-", ...) assert(_res == true and _err == nil) end | ||
51 | |||
52 | WR("chunk ", "Lane starts!\n") | ||
53 | |||
54 | local k,v | ||
55 | k,v=receive(); WR("chunk ", v.." received (expecting 1)\n"); assert(k and v==1) | ||
56 | k,v=receive(); WR("chunk ", v.." received (expecting 2)\n"); assert(k and v==2) | ||
57 | k,v=receive(); WR("chunk ", v.." received (expecting 3)\n"); assert(k and v==3) | ||
58 | k,v=receive(); WR("chunk ", tostring(v).." received (expecting nil from __lanesconvert)\n"); assert(k and v==nil, "table with __lanesconvert==lanes.null should be received as nil, got " .. tostring(v)) -- a table with __lanesconvert was sent | ||
59 | k,v=receive(); WR("chunk ", tostring(v).." received (expecting nil)\n"); assert(k and v==nil) | ||
60 | |||
61 | send(4,5,6); WR("chunk ", "4,5,6 sent\n") | ||
62 | send 'aaa'; WR("chunk ", "'aaa' sent\n") | ||
63 | send(nil); WR("chunk ", "nil sent\n") | ||
64 | send { 'a', 'b', 'c', d=10 }; WR("chunk ","{'a','b','c',d=10} sent\n") | ||
65 | |||
66 | k,v=receive(); WR("chunk ", v.." received\n"); assert(v==4) | ||
67 | |||
68 | local subT1 = { "subT1"} | ||
69 | local subT2 = { "subT2"} | ||
70 | send { subT1, subT2, subT1, subT2}; WR("chunk ", "{ subT1, subT2, subT1, subT2} sent\n") | ||
71 | |||
72 | WR("chunk ", "Lane ends!\n") | ||
73 | end | ||
74 | |||
75 | local linda = lanes_linda("communications") | ||
76 | assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications") | ||
77 | -- | ||
78 | -- ["->"] master -> slave | ||
79 | -- ["<-"] slave <- master | ||
80 | |||
81 | WR "test linda:get/set..." | ||
82 | linda:set("<->", "x", "y", "z") | ||
83 | local b,x,y,z = linda:get("<->", 1) | ||
84 | assert(b == 1 and x == "x" and y == nil and z == nil) | ||
85 | local b,x,y,z = linda:get("<->", 2) | ||
86 | assert(b == 2 and x == "x" and y == "y" and z == nil) | ||
87 | local b,x,y,z = linda:get("<->", 3) | ||
88 | assert(b == 3 and x == "x" and y == "y" and z == "z") | ||
89 | local b,x,y,z,w = linda:get("<->", 4) | ||
90 | assert(b == 3 and x == "x" and y == "y" and z == "z" and w == nil) | ||
91 | local k, x = linda:receive("<->") | ||
92 | assert(k == "<->" and x == "x") | ||
93 | local k,y,z = linda:receive(linda.batched, "<->", 2) | ||
94 | assert(k == "<->" and y == "y" and z == "z") | ||
95 | linda:set("<->") | ||
96 | local b,x,y,z,w = linda:get("<->", 4) | ||
97 | assert(b == 0 and x == nil and y == nil and z == nil and w == nil) | ||
98 | WR "ok\n" | ||
99 | |||
100 | local function PEEK(...) return linda:get("<-", ...) end | ||
101 | local function SEND(...) local _res, _err = linda:send("->", ...) assert(_res == true and _err == nil) end | ||
102 | local function RECEIVE() local k,v = linda:receive(1, "<-") return v end | ||
103 | |||
104 | local comms_lane = lanes_gen("io", {gc_cb = gc_cb, name = "auto"}, chunk)(linda) -- prepare & launch | ||
105 | |||
106 | SEND(1); WR("main ", "1 sent\n") | ||
107 | SEND(2); WR("main ", "2 sent\n") | ||
108 | SEND(3); WR("main ", "3 sent\n") | ||
109 | SEND(setmetatable({"should be ignored"},{__lanesconvert=lanes.null})); WR("main ", "__lanesconvert table sent\n") | ||
110 | for i=1,40 do | ||
111 | WR "." | ||
112 | SLEEP(0.0001) | ||
113 | assert(PEEK() == 0) -- nothing coming in, yet | ||
114 | end | ||
115 | SEND(nil); WR("\nmain ", "nil sent\n") | ||
116 | |||
117 | local a,b,c = RECEIVE(), RECEIVE(), RECEIVE() | ||
118 | |||
119 | print("lane status: " .. comms_lane.status) | ||
120 | if comms_lane.status == "error" then | ||
121 | print(comms_lane:join()) | ||
122 | else | ||
123 | WR("main ", tostring(a)..", "..tostring(b)..", "..tostring(c).." received\n") | ||
124 | end | ||
125 | |||
126 | assert(a==4 and b==5 and c==6) | ||
127 | |||
128 | local aaa = RECEIVE(); WR("main ", aaa.." received\n") | ||
129 | assert(aaa=='aaa') | ||
130 | |||
131 | local null = RECEIVE(); WR(tostring(null).." received\n") | ||
132 | assert(null==nil) | ||
133 | |||
134 | local out_t = RECEIVE(); WR(type(out_t).." received\n") | ||
135 | assert(tables_match(out_t, {'a','b','c',d=10})) | ||
136 | |||
137 | assert(PEEK() == 0) | ||
138 | SEND(4) | ||
139 | |||
140 | local complex_table = RECEIVE(); WR(type(complex_table).." received\n") | ||
141 | assert(complex_table[1] == complex_table[3] and complex_table[2] == complex_table[4]) | ||
142 | WR(table.concat({complex_table[1][1],complex_table[2][1],complex_table[3][1],complex_table[4][1]},", ")) | ||
143 | |||
144 | WR("collectgarbage") | ||
145 | comms_lane = nil | ||
146 | collectgarbage() | ||
147 | -- wait | ||
148 | WR("waiting 1s") | ||
149 | SLEEP(1) | ||
diff --git a/unit_tests/scripts/lane/tasking_error.lua b/unit_tests/scripts/lane/tasking_error.lua new file mode 100644 index 0000000..1e2347f --- /dev/null +++ b/unit_tests/scripts/lane/tasking_error.lua | |||
@@ -0,0 +1,49 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local lanes_gen = assert(lanes.gen) | ||
12 | local lanes_linda = assert(lanes.linda) | ||
13 | |||
14 | -- ################################################################################################## | ||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | |||
18 | local gc_cb = function(name_, status_) | ||
19 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
20 | end | ||
21 | |||
22 | PRINT("---=== Tasking (error) ===---", "\n\n") | ||
23 | |||
24 | -- a lane that throws immediately the error value it received | ||
25 | local g = lanes_gen("", {gc_cb = gc_cb}, error) | ||
26 | local errmsg = "ERROR!" | ||
27 | |||
28 | if true then | ||
29 | -- if you index an errored lane, it should throw the error again | ||
30 | local lane = g(errmsg) | ||
31 | assert.fails(function() return lane[1] end) | ||
32 | assert(lane.status == "error") | ||
33 | -- even after indexing, joining a lane in error should give nil,<error> | ||
34 | local a,b = lane:join() | ||
35 | assert(a == nil and string.find(b, errmsg)) | ||
36 | end | ||
37 | |||
38 | if true then | ||
39 | local lane = g(errmsg) | ||
40 | -- after indexing, joining a lane in error should give nil,<error> | ||
41 | local a, b = lane:join() | ||
42 | assert(lane.status == "error") | ||
43 | assert(a == nil and string.find(b, errmsg)) | ||
44 | -- even after joining, indexing should raise an error | ||
45 | assert.fails(function() return lane[1] end) | ||
46 | -- unless we index with a negative value to get the error message | ||
47 | local c = lane[-1] | ||
48 | assert(c == b) | ||
49 | end | ||
diff --git a/unit_tests/scripts/lane/tasking_join_test.lua b/unit_tests/scripts/lane/tasking_join_test.lua new file mode 100644 index 0000000..8f2d4db --- /dev/null +++ b/unit_tests/scripts/lane/tasking_join_test.lua | |||
@@ -0,0 +1,65 @@ | |||
1 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
2 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
6 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
7 | |||
8 | local utils = lanes.require "_utils" | ||
9 | local PRINT = utils.MAKE_PRINT() | ||
10 | |||
11 | local lanes_gen = assert(lanes.gen) | ||
12 | local lanes_linda = assert(lanes.linda) | ||
13 | |||
14 | -- ################################################################################################## | ||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | |||
18 | local gc_cb = function(name_, status_) | ||
19 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
20 | end | ||
21 | |||
22 | -- ################################################################################################## | ||
23 | -- ################################################################################################## | ||
24 | -- ################################################################################################## | ||
25 | |||
26 | local SLEEP = function(...) | ||
27 | local k, v = lanes.sleep(...) | ||
28 | assert(k == nil and v == "timeout") | ||
29 | end | ||
30 | |||
31 | PRINT("---=== :join test ===---", "\n\n") | ||
32 | |||
33 | -- NOTE: 'unpack()' cannot be used on the lane handle; it will always return nil | ||
34 | -- (unless [1..n] has been read earlier, in which case it would seemingly | ||
35 | -- work). | ||
36 | |||
37 | local S= lanes_gen("table", {gc_cb = gc_cb}, | ||
38 | function(arg) | ||
39 | lane_threadname "join test lane" | ||
40 | set_finalizer(function() end) | ||
41 | local aux= {} | ||
42 | for i, v in ipairs(arg) do | ||
43 | table.insert(aux, 1, v) | ||
44 | end | ||
45 | -- unpack was renamed table.unpack in Lua 5.2: cater for both! | ||
46 | return (unpack or table.unpack)(aux) | ||
47 | end) | ||
48 | |||
49 | h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values | ||
50 | -- wait a bit so that the lane has a chance to set its debug name | ||
51 | SLEEP(0.5) | ||
52 | print("joining with '" .. h:get_threadname() .. "'") | ||
53 | local a,b,c,d= h:join() | ||
54 | if h.status == "error" then | ||
55 | print(h:get_threadname(), "error: " , a, b, c, d) | ||
56 | else | ||
57 | print(h:get_threadname(), a,b,c,d) | ||
58 | assert(a==14, "a == " .. tostring(a)) | ||
59 | assert(b==13, "b == " .. tostring(b)) | ||
60 | assert(c==12, "c == " .. tostring(c)) | ||
61 | assert(d==nil, "d == " .. tostring(d)) | ||
62 | end | ||
63 | |||
64 | local nameof_type, nameof_name = lanes.nameof(print) | ||
65 | PRINT("name of " .. nameof_type .. " print = '" .. nameof_name .. "'") | ||
diff --git a/unit_tests/scripts/lane/tasking_send_receive_code.lua b/unit_tests/scripts/lane/tasking_send_receive_code.lua new file mode 100644 index 0000000..77a4b12 --- /dev/null +++ b/unit_tests/scripts/lane/tasking_send_receive_code.lua | |||
@@ -0,0 +1,91 @@ | |||
1 | local config = { with_timers = false, strip_functions = false, internal_allocator = "libc"} | ||
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure() | ||
3 | print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2) | ||
4 | local lanes = require_lanes_result_1 | ||
5 | |||
6 | local require_assert_result_1, require_assert_result_2 = require "_assert" | ||
7 | print("require_assert_result:", require_assert_result_1, require_assert_result_2) | ||
8 | |||
9 | local utils = lanes.require "_utils" | ||
10 | local PRINT = utils.MAKE_PRINT() | ||
11 | |||
12 | local lanes_gen = assert(lanes.gen) | ||
13 | local lanes_linda = assert(lanes.linda) | ||
14 | |||
15 | -- ################################################################################################## | ||
16 | -- ################################################################################################## | ||
17 | -- ################################################################################################## | ||
18 | |||
19 | local gc_cb = function(name_, status_) | ||
20 | PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'") | ||
21 | end | ||
22 | |||
23 | -- ################################################################################################## | ||
24 | -- ################################################################################################## | ||
25 | -- ################################################################################################## | ||
26 | |||
27 | PRINT("---=== Receive & send of code ===---", "\n") | ||
28 | |||
29 | local upvalue = "123" | ||
30 | local tostring = tostring | ||
31 | |||
32 | local function chunk2(linda) | ||
33 | local utils = require "_utils" | ||
34 | local PRINT = utils.MAKE_PRINT() | ||
35 | PRINT("here") | ||
36 | assert(upvalue == "123") -- even when running as separate thread | ||
37 | -- function name & line number should be there even as separate thread | ||
38 | -- | ||
39 | local info= debug.getinfo(1) -- 1 = us | ||
40 | -- | ||
41 | PRINT("linda named-> '" ..tostring(linda).."'") | ||
42 | PRINT "debug.getinfo->" | ||
43 | for k,v in pairs(info) do PRINT(k,v) end | ||
44 | |||
45 | -- some assertions are adjusted depending on config.strip_functions, because it changes what we get out of debug.getinfo | ||
46 | assert(info.nups == (_VERSION == "Lua 5.1" and 3 or 4), "bad nups " .. info.nups) -- upvalue + config + tostring + _ENV (Lua > 5.2 only) | ||
47 | assert(info.what == "Lua", "bad what") | ||
48 | --assert(info.name == "chunk2") -- name does not seem to come through | ||
49 | assert(config.strip_functions and info.source=="=?" or string.match(info.source, "^@.*tasking_send_receive_code.lua$"), "bad info.source " .. info.source) | ||
50 | assert(config.strip_functions and info.short_src=="?" or string.match(info.short_src, "^.*tasking_send_receive_code.lua$"), "bad info.short_src " .. info.short_src) | ||
51 | -- These vary so let's not be picky (they're there..) | ||
52 | -- | ||
53 | assert(info.linedefined == 32, "bad linedefined") -- start of 'chunk2' | ||
54 | assert(config.strip_functions and info.currentline==-1 or info.currentline > info.linedefined, "bad currentline") -- line of 'debug.getinfo' | ||
55 | assert(info.lastlinedefined > info.currentline, "bad lastlinedefined") -- end of 'chunk2' | ||
56 | local k,func= linda:receive("down") | ||
57 | assert(type(func)=="function", "not a function") | ||
58 | assert(k=="down") | ||
59 | |||
60 | func(linda) | ||
61 | |||
62 | local k,str= linda:receive("down") | ||
63 | assert(str=="ok", "bad receive result") | ||
64 | |||
65 | linda:send("up", function() return ":)" end, "ok2") | ||
66 | end | ||
67 | |||
68 | local linda = lanes_linda("auto") | ||
69 | local t2= lanes_gen("debug,package,string,io", {gc_cb = gc_cb}, chunk2)(linda) -- prepare & launch | ||
70 | linda:send("down", function(linda) linda:send("up", "ready!") end, | ||
71 | "ok") | ||
72 | -- wait to see if the tiny function gets executed | ||
73 | -- | ||
74 | local k,s= linda:receive(1, "up") | ||
75 | if t2.status == "error" then | ||
76 | PRINT("t2 error: " , t2:join()) | ||
77 | assert(false) | ||
78 | end | ||
79 | PRINT(s) | ||
80 | assert(s=="ready!", s .. " is not 'ready!'") | ||
81 | |||
82 | -- returns of the 'chunk2' itself | ||
83 | -- | ||
84 | local k,f= linda:receive("up") | ||
85 | assert(type(f)=="function") | ||
86 | |||
87 | local s2= f() | ||
88 | assert(s2==":)") | ||
89 | |||
90 | local k,ok2= linda:receive("up") | ||
91 | assert(ok2 == "ok2") | ||
diff --git a/unit_tests/scripts/lane/uncooperative_shutdown.lua b/unit_tests/scripts/lane/uncooperative_shutdown.lua new file mode 100644 index 0000000..ce7df57 --- /dev/null +++ b/unit_tests/scripts/lane/uncooperative_shutdown.lua | |||
@@ -0,0 +1,24 @@ | |||
1 | if not package.preload.fixture then | ||
2 | error "can't be run outside of UnitTest framework" | ||
3 | end | ||
4 | local fixture = require "fixture" | ||
5 | |||
6 | local lanes = require "lanes".configure{shutdown_timeout = 0.001, on_state_create = fixture.on_state_create} | ||
7 | |||
8 | -- launch lanes that blocks forever | ||
9 | local lane = function() | ||
10 | local fixture = require "fixture" | ||
11 | fixture.forever() | ||
12 | end | ||
13 | |||
14 | -- the generator | ||
15 | local g1 = lanes.gen("*", lane) | ||
16 | |||
17 | -- launch lane | ||
18 | local h1 = g1() | ||
19 | |||
20 | -- wait until the lane is running | ||
21 | repeat until h1.status == "running" | ||
22 | |||
23 | -- let the script end, Lanes should throw std::logic_error because the lane did not gracefully terminate | ||
24 | lanes.finally(fixture.throwing_finalizer) | ||
diff --git a/unit_tests/scripts/linda/multiple_keepers.lua b/unit_tests/scripts/linda/multiple_keepers.lua new file mode 100644 index 0000000..8733087 --- /dev/null +++ b/unit_tests/scripts/linda/multiple_keepers.lua | |||
@@ -0,0 +1,26 @@ | |||
1 | -- 3 keepers in addition to the one reserved for the timer linda | ||
2 | local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500} | ||
3 | local lanes = require_lanes_result_1 | ||
4 | |||
5 | local a = lanes.linda("A", 1) | ||
6 | local b = lanes.linda("B", 2) | ||
7 | local c = lanes.linda("C", 3) | ||
8 | |||
9 | -- store each linda in the other 2 | ||
10 | do | ||
11 | a:set("kA", a, b, c) | ||
12 | local nA, rA, rB, rC = a:get("kA", 3) | ||
13 | assert(nA == 3 and rA == a and rB == b and rC == c) | ||
14 | end | ||
15 | |||
16 | do | ||
17 | b:set("kB", a, b, c) | ||
18 | local nB, rA, rB, rC = b:get("kB", 3) | ||
19 | assert(nB == 3 and rA == a and rB == b and rC == c) | ||
20 | end | ||
21 | |||
22 | do | ||
23 | c:set("kC", a, b, c) | ||
24 | local nC, rA, rB, rC = c:get("kC", 3) | ||
25 | assert(nC == 3 and rA == a and rB == b and rC == c) | ||
26 | end | ||
diff --git a/unit_tests/scripts/linda/send_receive.lua b/unit_tests/scripts/linda/send_receive.lua new file mode 100644 index 0000000..9a6a7be --- /dev/null +++ b/unit_tests/scripts/linda/send_receive.lua | |||
@@ -0,0 +1,46 @@ | |||
1 | local lanes = require "lanes" | ||
2 | |||
3 | -- a newly created linda doesn't contain anything | ||
4 | local l = lanes.linda() | ||
5 | assert(l.null == lanes.null) | ||
6 | assert(l:dump() == nil) | ||
7 | |||
8 | -- read something with 0 timeout, should fail | ||
9 | local k,v = l:receive(0, "k") | ||
10 | assert(k == nil and v == 'timeout') | ||
11 | |||
12 | -- send some value | ||
13 | assert(l:send("k1", 99) == true) | ||
14 | -- make sure the contents look as expected | ||
15 | local t = l:dump() | ||
16 | assert(type(t) == 'table') | ||
17 | local tk1 = t.k1 | ||
18 | assert(type(tk1) == 'table') | ||
19 | assert(tk1.first == 1 and tk1.count == 1 and tk1.limit == 'unlimited' and tk1.restrict == 'none') | ||
20 | assert(#tk1.fifo == tk1.count, #tk1.fifo .. " ~= " .. tk1.count) | ||
21 | assert(tk1.fifo[1] == 99, tk1.fifo[1] .. " ~= " .. 99) | ||
22 | |||
23 | -- read the value, should succeed | ||
24 | local k,v = l:receive("k1") | ||
25 | assert(k == "k1" and v == 99) | ||
26 | -- after reading, the data is no longer available (even if the key itself still exists within the linda) | ||
27 | local t = l:dump() | ||
28 | assert(type(t) == 'table') | ||
29 | local tk1 = t.k1 | ||
30 | assert(type(tk1) == 'table') | ||
31 | assert(tk1.first == 1 and tk1.count == 0 and tk1.limit == 'unlimited' and tk1.restrict == 'none') | ||
32 | assert(#tk1.fifo == tk1.count and tk1.fifo[1] == nil) | ||
33 | -- read again, should fail | ||
34 | local k,v = l:receive(0, "k1") | ||
35 | assert(k == nil and v == 'timeout') | ||
36 | |||
37 | -- send null, read nil | ||
38 | l:send("k", l.null) | ||
39 | local k,v = l:receive(0, "k") | ||
40 | assert(k == "k" and v == nil) | ||
41 | |||
42 | -- using a deep userdata (such as another linda) as key, should work | ||
43 | local l2 = lanes.linda() | ||
44 | l:send(nil, l2, 99) | ||
45 | local k,v = l:receive(0, l2) | ||
46 | assert(k == l2 and v == 99) | ||
diff --git a/unit_tests/scripts/linda/send_registered_userdata.lua b/unit_tests/scripts/linda/send_registered_userdata.lua new file mode 100644 index 0000000..2c0195a --- /dev/null +++ b/unit_tests/scripts/linda/send_registered_userdata.lua | |||
@@ -0,0 +1,5 @@ | |||
1 | local lanes = require 'lanes'.configure{with_timers = false} | ||
2 | local l = lanes.linda'gleh' | ||
3 | l:set('yo', io.stdin) | ||
4 | local n, stdin_out = l:get('yo') | ||
5 | assert(n == 1 and stdin_out == io.stdin, tostring(stdin_out) .. " ~= " .. tostring(io.stdin)) | ||
diff --git a/unit_tests/shared.cpp b/unit_tests/shared.cpp new file mode 100644 index 0000000..14b2b13 --- /dev/null +++ b/unit_tests/shared.cpp | |||
@@ -0,0 +1,360 @@ | |||
1 | #include "_pch.hpp" | ||
2 | |||
3 | #include "shared.h" | ||
4 | |||
5 | // ################################################################################################# | ||
6 | // ################################################################################################# | ||
7 | // internal fixture module | ||
8 | // ################################################################################################# | ||
9 | // ################################################################################################# | ||
10 | |||
11 | LANES_API int luaopen_deep_test(lua_State* L_); | ||
12 | |||
13 | namespace | ||
14 | { | ||
15 | extern int luaopen_fixture(lua_State*); | ||
16 | namespace local { | ||
17 | void PreloadModule(lua_State* const L_, std::string_view const& name_, lua_CFunction const openf_) | ||
18 | { | ||
19 | STACK_CHECK_START_REL(L_, 0); | ||
20 | lua_getglobal(L_, "package"); // L_: package | ||
21 | luaG_getfield(L_, kIdxTop, "preload"); // L_: package package.preload | ||
22 | lua_pushcfunction(L_, openf_); // L_: package package.preload openf_ | ||
23 | luaG_setfield(L_, StackIndex{ -2 }, name_); // L_: package package.preload | ||
24 | lua_pop(L_, 2); | ||
25 | STACK_CHECK(L_, 0); | ||
26 | } | ||
27 | |||
28 | |||
29 | static std::map<lua_State*, std::atomic_flag> sFinalizerHits; | ||
30 | static std::mutex sCallCountsLock; | ||
31 | |||
32 | // a finalizer that we can detect even after closing the state | ||
33 | lua_CFunction sThrowingFinalizer = +[](lua_State* L_) { | ||
34 | std::lock_guard _guard{ sCallCountsLock }; | ||
35 | sFinalizerHits[L_].test_and_set(); | ||
36 | luaG_pushstring(L_, "throw"); | ||
37 | return 1; | ||
38 | }; | ||
39 | |||
40 | // a finalizer that we can detect even after closing the state | ||
41 | lua_CFunction sYieldingFinalizer = +[](lua_State* L_) { | ||
42 | std::lock_guard _guard{ sCallCountsLock }; | ||
43 | sFinalizerHits[L_].test_and_set(); | ||
44 | return 0; | ||
45 | }; | ||
46 | |||
47 | // a function that runs forever | ||
48 | lua_CFunction sForever = +[](lua_State* L_) { | ||
49 | while (true) { | ||
50 | std::this_thread::yield(); | ||
51 | } | ||
52 | return 0; | ||
53 | }; | ||
54 | |||
55 | lua_CFunction sNewLightUserData = +[](lua_State* const L_) { | ||
56 | lua_pushlightuserdata(L_, std::bit_cast<void*>(static_cast<uintptr_t>(42))); | ||
57 | return 1; | ||
58 | }; | ||
59 | |||
60 | lua_CFunction sNewUserData = +[](lua_State* const L_) { | ||
61 | std::ignore = luaG_newuserdatauv<int>(L_, UserValueCount{ 0 }); | ||
62 | return 1; | ||
63 | }; | ||
64 | |||
65 | // a function that enables any lane to require "fixture" | ||
66 | lua_CFunction sOnStateCreate = +[](lua_State* const L_) { | ||
67 | PreloadModule(L_, "fixture", luaopen_fixture); | ||
68 | PreloadModule(L_, "deep_test", luaopen_deep_test); | ||
69 | return 0; | ||
70 | }; | ||
71 | |||
72 | static luaL_Reg const sFixture[] = { | ||
73 | { "forever", sForever }, | ||
74 | { "newlightuserdata", sNewLightUserData }, | ||
75 | { "newuserdata", sNewUserData }, | ||
76 | { "on_state_create", sOnStateCreate }, | ||
77 | { "throwing_finalizer", sThrowingFinalizer }, | ||
78 | { "yielding_finalizer", sYieldingFinalizer }, | ||
79 | { nullptr, nullptr } | ||
80 | }; | ||
81 | } // namespace local | ||
82 | |||
83 | // ############################################################################################ | ||
84 | |||
85 | int luaopen_fixture(lua_State* L_) | ||
86 | { | ||
87 | STACK_CHECK_START_REL(L_, 0); | ||
88 | luaG_newlib<std::size(local::sFixture)>(L_, local::sFixture); // M | ||
89 | STACK_CHECK(L_, 1); | ||
90 | return 1; | ||
91 | } | ||
92 | |||
93 | } // namespace | ||
94 | |||
95 | // ################################################################################################# | ||
96 | // ################################################################################################# | ||
97 | // Internals | ||
98 | // ################################################################################################# | ||
99 | // ################################################################################################# | ||
100 | |||
101 | TEST(Internals, StackChecker) | ||
102 | { | ||
103 | LuaState _L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
104 | StackChecker::CallsCassert = false; | ||
105 | |||
106 | auto _doStackCheckerTest = [&_L](lua_CFunction const _f, LuaError const _expected) { | ||
107 | lua_pushcfunction(_L, _f); | ||
108 | ASSERT_EQ(ToLuaError(lua_pcall(_L, 0, 0, 0)), _expected); | ||
109 | }; | ||
110 | |||
111 | // function where the StackChecker detects something wrong with the stack | ||
112 | lua_CFunction _unbalancedStack1 = +[](lua_State* const _L) { | ||
113 | // record current position | ||
114 | STACK_CHECK_START_REL(_L, 0); | ||
115 | // push something | ||
116 | lua_newtable(_L); | ||
117 | // check if we are at the same position as before (no) | ||
118 | STACK_CHECK(_L, 0); | ||
119 | return 1; | ||
120 | }; | ||
121 | |||
122 | // function where the StackChecker detects no issue | ||
123 | lua_CFunction _balancedStack1 = +[](lua_State* const _L) { | ||
124 | // record current position | ||
125 | STACK_CHECK_START_REL(_L, 0); | ||
126 | // check if we are at the same position as before (yes) | ||
127 | STACK_CHECK(_L, 0); | ||
128 | return 0; | ||
129 | }; | ||
130 | |||
131 | lua_CFunction _goodStart = +[](lua_State* const _L) { | ||
132 | // check that the stack ends at the specified position, and record that as our reference point | ||
133 | STACK_CHECK_START_ABS(_L, 0); | ||
134 | // check if we are at the same position as before (yes) | ||
135 | STACK_CHECK(_L, 0); | ||
136 | return 0; | ||
137 | }; | ||
138 | |||
139 | lua_CFunction _badStart = +[](lua_State* const _L) { | ||
140 | // check that the stack ends at the specified position (no), and record that as our reference point | ||
141 | STACK_CHECK_START_ABS(_L, 1); | ||
142 | // check if we are at the same position as before (yes) | ||
143 | STACK_CHECK(_L, 0); | ||
144 | return 0; | ||
145 | }; | ||
146 | |||
147 | _doStackCheckerTest(_unbalancedStack1, LuaError::ERRRUN); | ||
148 | _doStackCheckerTest(_balancedStack1, LuaError::OK); | ||
149 | _doStackCheckerTest(_goodStart, LuaError::OK); | ||
150 | _doStackCheckerTest(_badStart, LuaError::ERRRUN); | ||
151 | } | ||
152 | |||
153 | // ################################################################################################# | ||
154 | // ################################################################################################# | ||
155 | // LuaState | ||
156 | // ################################################################################################# | ||
157 | // ################################################################################################# | ||
158 | |||
159 | LuaState::LuaState(WithBaseLibs const withBaseLibs_, WithFixture const withFixture_) | ||
160 | { | ||
161 | STACK_CHECK_START_REL(L, 0); | ||
162 | if (withBaseLibs_) { | ||
163 | luaL_openlibs(L); | ||
164 | } | ||
165 | if (withFixture_) { | ||
166 | // make require "fixture" call luaopen_fixture | ||
167 | local::PreloadModule(L, "fixture", luaopen_fixture); | ||
168 | local::PreloadModule(L, "deep_test", luaopen_deep_test); | ||
169 | } | ||
170 | STACK_CHECK(L, 0); | ||
171 | } | ||
172 | |||
173 | // ################################################################################################# | ||
174 | |||
175 | void LuaState::close() | ||
176 | { | ||
177 | if (L) { | ||
178 | lua_close(L); | ||
179 | |||
180 | { | ||
181 | std::lock_guard _guard{ local::sCallCountsLock }; | ||
182 | finalizerWasCalled = local::sFinalizerHits[L].test(); | ||
183 | local::sFinalizerHits.erase(L); | ||
184 | } | ||
185 | |||
186 | L = nullptr; | ||
187 | } | ||
188 | } | ||
189 | |||
190 | // ################################################################################################# | ||
191 | |||
192 | LuaError LuaState::doString(std::string_view const& str_) const | ||
193 | { | ||
194 | lua_settop(L, 0); | ||
195 | if (str_.empty()) { | ||
196 | lua_pushnil(L); | ||
197 | return LuaError::OK; | ||
198 | } | ||
199 | STACK_CHECK_START_REL(L, 0); | ||
200 | LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk() | ||
201 | if (_loadErr != LuaError::OK) { | ||
202 | STACK_CHECK(L, 1); // the error message is on the stack | ||
203 | return _loadErr; | ||
204 | } | ||
205 | LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"? | ||
206 | [[maybe_unused]] std::string_view const _out{ luaG_tostring(L, kIdxTop) }; | ||
207 | STACK_CHECK(L, 1); | ||
208 | return _callErr; | ||
209 | } | ||
210 | |||
211 | // ################################################################################################# | ||
212 | |||
213 | std::string_view LuaState::doStringAndRet(std::string_view const& str_) const | ||
214 | { | ||
215 | lua_settop(L, 0); | ||
216 | if (str_.empty()) { | ||
217 | luaG_pushstring(L, ""); | ||
218 | return luaG_tostring(L, kIdxTop); | ||
219 | } | ||
220 | STACK_CHECK_START_REL(L, 0); | ||
221 | LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk() | ||
222 | if (_loadErr != LuaError::OK) { | ||
223 | STACK_CHECK(L, 1); // the error message is on the stack | ||
224 | return ""; | ||
225 | } | ||
226 | LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"?|retstring | ||
227 | STACK_CHECK(L, 1); | ||
228 | return luaG_tostring(L, kIdxTop); | ||
229 | } | ||
230 | |||
231 | // ################################################################################################# | ||
232 | |||
233 | LuaError LuaState::doFile(std::filesystem::path const& root_, std::string_view const& str_) const | ||
234 | { | ||
235 | lua_settop(L, 0); | ||
236 | if (str_.empty()) { | ||
237 | lua_pushnil(L); | ||
238 | return LuaError::OK; | ||
239 | } | ||
240 | STACK_CHECK_START_REL(L, 0); | ||
241 | std::filesystem::path _combined{ root_ }; | ||
242 | _combined.append(str_); | ||
243 | _combined.replace_extension(".lua"); | ||
244 | LuaError const _loadErr{ luaL_loadfile(L, _combined.generic_string().c_str()) }; // L: chunk() | ||
245 | if (_loadErr != LuaError::OK) { | ||
246 | STACK_CHECK(L, 1); | ||
247 | return _loadErr; | ||
248 | } | ||
249 | LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"? | ||
250 | STACK_CHECK(L, 1); // either nil, a return value, or an error string | ||
251 | return _callErr; | ||
252 | } | ||
253 | |||
254 | // ################################################################################################# | ||
255 | |||
256 | LuaError LuaState::loadString(std::string_view const& str_) const | ||
257 | { | ||
258 | lua_settop(L, 0); | ||
259 | if (str_.empty()) { | ||
260 | // this particular test is disabled: just create a dummy function that will run without error | ||
261 | lua_pushcfunction(L, +[](lua_State*){return 0;}); | ||
262 | return LuaError::OK; | ||
263 | } | ||
264 | STACK_CHECK_START_REL(L, 0); | ||
265 | LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk() | ||
266 | STACK_CHECK(L, 1); // function on success, error string on failure | ||
267 | return _loadErr; | ||
268 | } | ||
269 | |||
270 | // ################################################################################################# | ||
271 | |||
272 | LuaError LuaState::loadFile(std::filesystem::path const& root_, std::string_view const& str_) const | ||
273 | { | ||
274 | lua_settop(L, 0); | ||
275 | STACK_CHECK_START_REL(L, 0); | ||
276 | if (str_.empty()) { | ||
277 | // this particular test is disabled: just create a dummy function that will run without error | ||
278 | lua_pushcfunction(L, +[](lua_State*){return 0;}); | ||
279 | return LuaError::OK; | ||
280 | } | ||
281 | |||
282 | std::filesystem::path _combined{ root_ }; | ||
283 | _combined.append(str_); | ||
284 | _combined.replace_extension(".lua"); | ||
285 | LuaError const _loadErr{ luaL_loadfile(L, _combined.generic_string().c_str()) }; // L: chunk() | ||
286 | STACK_CHECK(L, 1); // function on success, error string on failure | ||
287 | return _loadErr; | ||
288 | } | ||
289 | |||
290 | // ################################################################################################# | ||
291 | |||
292 | LuaError LuaState::runChunk() const | ||
293 | { | ||
294 | STACK_CHECK_START_ABS(L, 1); // we must start with the chunk on the stack (or an error string if it failed to load) | ||
295 | LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"? | ||
296 | STACK_CHECK(L, 1); | ||
297 | return _callErr; | ||
298 | } | ||
299 | |||
300 | // ################################################################################################# | ||
301 | // ################################################################################################# | ||
302 | |||
303 | TEST(LuaState, DoString) | ||
304 | { | ||
305 | LuaState _L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | ||
306 | // if the script fails to load, we should find the error message at the top of the stack | ||
307 | ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("function end"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::STRING; }()); | ||
308 | |||
309 | // if the script runs, the stack should contain its return value | ||
310 | ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("return true"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::BOOLEAN; }()); | ||
311 | ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("return 'hello'"); return lua_gettop(L) == 1 && luaG_tostring(L, StackIndex{1}) == "hello"; }()); | ||
312 | // or nil if it didn't return anything | ||
313 | ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("return"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::NIL; }()); | ||
314 | |||
315 | // on failure, doStringAndRet returns "", and the error message is on the stack | ||
316 | ASSERT_TRUE([&L = _L]() { return L.doStringAndRet("function end") == "" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::STRING && luaG_tostring(L, StackIndex{1}) != ""; }()); | ||
317 | // on success doStringAndRet returns the string returned by the script, that is also at the top of the stack | ||
318 | ASSERT_TRUE([&L = _L]() { return L.doStringAndRet("return 'hello'") == "hello" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::STRING && luaG_tostring(L, StackIndex{1}) == "hello"; }()); | ||
319 | // if the returned value is not (convertible to) a string, we should get an empty string out of doStringAndRet | ||
320 | ASSERT_TRUE([&L = _L]() { return L.doStringAndRet("return function() end") == "" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::FUNCTION && luaG_tostring(L, StackIndex{1}) == ""; }()); | ||
321 | } | ||
322 | |||
323 | // ################################################################################################# | ||
324 | // ################################################################################################# | ||
325 | // UnitTestRunner | ||
326 | // ################################################################################################# | ||
327 | // ################################################################################################# | ||
328 | |||
329 | UnitTestRunner::UnitTestRunner() | ||
330 | { | ||
331 | [[maybe_unused]] std::filesystem::path const _current{ std::filesystem::current_path() }; | ||
332 | std::filesystem::path _assertPath{ R"(.\lanes\unit_tests\scripts)" }; | ||
333 | // I need to append that path to the list of locations where modules can be required | ||
334 | // so that the scripts can require "_assert" and find _assert.lua (same with "_utils.lua") | ||
335 | std::string _script{ "package.path = package.path.." }; | ||
336 | _script += "';"; | ||
337 | _script += std::filesystem::canonical(_assertPath).generic_string(); | ||
338 | _script += "/?.lua'"; | ||
339 | std::ignore = L.doString(_script.c_str()); | ||
340 | |||
341 | root = std::filesystem::canonical(R"(.\lanes\unit_tests\scripts)").generic_string(); | ||
342 | } | ||
343 | |||
344 | // ################################################################################################# | ||
345 | |||
346 | TEST_P(UnitTestRunner, ScriptedTest) | ||
347 | { | ||
348 | FileRunnerParam const& _param = GetParam(); | ||
349 | switch (_param.test) { | ||
350 | case TestType::AssertNoLuaError: | ||
351 | ASSERT_EQ(L.doFile(root, _param.script), LuaError::OK) << L; | ||
352 | break; | ||
353 | case TestType::AssertNoThrow: | ||
354 | ASSERT_NO_THROW((std::ignore = L.doFile(root, _param.script), L.close())) << L; | ||
355 | break; | ||
356 | case TestType::AssertThrows: | ||
357 | ASSERT_THROW((std::ignore = L.doFile(root, _param.script), L.close()), std::logic_error) << L; | ||
358 | break; | ||
359 | } | ||
360 | } | ||
diff --git a/unit_tests/shared.h b/unit_tests/shared.h new file mode 100644 index 0000000..bf3a747 --- /dev/null +++ b/unit_tests/shared.h | |||
@@ -0,0 +1,116 @@ | |||
1 | #pragma once | ||
2 | |||
3 | // ################################################################################################# | ||
4 | |||
5 | class LuaState | ||
6 | { | ||
7 | public: | ||
8 | |||
9 | DECLARE_UNIQUE_TYPE(WithBaseLibs, bool); | ||
10 | DECLARE_UNIQUE_TYPE(WithFixture, bool); | ||
11 | lua_State* L{ luaL_newstate() }; | ||
12 | bool finalizerWasCalled{}; | ||
13 | STACK_CHECK_START_REL(L, 0); | ||
14 | |||
15 | ~LuaState() | ||
16 | { | ||
17 | close(); | ||
18 | } | ||
19 | LuaState(WithBaseLibs withBaseLibs_, WithFixture withFixture_); | ||
20 | |||
21 | LuaState(LuaState&& rhs_) = default; | ||
22 | |||
23 | operator lua_State*() const { return L; } | ||
24 | |||
25 | void stackCheck(int delta_) { STACK_CHECK(L, delta_); } | ||
26 | void close(); | ||
27 | |||
28 | // all these methods leave a single item on the stack: an error string on failure, or a single value that depends on what we do | ||
29 | [[nodiscard]] | ||
30 | LuaError doString(std::string_view const& str_) const; | ||
31 | std::string_view doStringAndRet(std::string_view const& str_) const; | ||
32 | [[nodiscard]] | ||
33 | LuaError doFile(std::filesystem::path const& root_, std::string_view const& str_) const; | ||
34 | [[nodiscard]] | ||
35 | LuaError loadString(std::string_view const& str_) const; | ||
36 | [[nodiscard]] | ||
37 | LuaError loadFile(std::filesystem::path const& root_, std::string_view const& str_) const; | ||
38 | [[nodiscard]] | ||
39 | LuaError runChunk() const; | ||
40 | |||
41 | friend std::ostream& operator<<(std::ostream& os_, LuaState const& s_) | ||
42 | { | ||
43 | os_ << luaG_tostring(s_.L, kIdxTop); | ||
44 | return os_; | ||
45 | } | ||
46 | }; | ||
47 | |||
48 | #define LUA_EXPECT_SUCCESS(S_, WHAT_) { LuaState S{ std::move(S_) }; EXPECT_EQ(S.WHAT_, LuaError::OK) << S; } | ||
49 | #define LUA_EXPECT_FAILURE(S_, WHAT_) { LuaState S{ std::move(S_) }; EXPECT_NE(S.WHAT_, LuaError::OK) << S; } | ||
50 | |||
51 | // ################################################################################################# | ||
52 | |||
53 | enum class TestType | ||
54 | { | ||
55 | AssertNoLuaError, | ||
56 | AssertNoThrow, | ||
57 | AssertThrows, | ||
58 | }; | ||
59 | |||
60 | struct FileRunnerParam | ||
61 | { | ||
62 | std::string_view script; | ||
63 | TestType test; | ||
64 | }; | ||
65 | |||
66 | class FileRunner : public ::testing::TestWithParam<FileRunnerParam> | ||
67 | { | ||
68 | protected: | ||
69 | LuaState L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ true } }; | ||
70 | std::string root; | ||
71 | |||
72 | ~FileRunner() override | ||
73 | { | ||
74 | lua_settop(L, 0); | ||
75 | } | ||
76 | void SetUp() override | ||
77 | { | ||
78 | lua_settop(L, 0); | ||
79 | } | ||
80 | |||
81 | void TearDown() override | ||
82 | { | ||
83 | lua_settop(L, 0); | ||
84 | } | ||
85 | }; | ||
86 | |||
87 | // ################################################################################################# | ||
88 | |||
89 | class UnitTestRunner : public FileRunner | ||
90 | { | ||
91 | public: | ||
92 | UnitTestRunner(); | ||
93 | }; | ||
94 | |||
95 | // ################################################################################################# | ||
96 | |||
97 | // Can't #ifdef stuff away inside a macro expansion | ||
98 | #if LUA_VERSION_NUM == 501 | ||
99 | #define LUA51_ONLY(a) a | ||
100 | #else | ||
101 | #define LUA51_ONLY(a) "" | ||
102 | #endif | ||
103 | |||
104 | #if LUA_VERSION_NUM == 504 | ||
105 | #define LUA54_ONLY(a) a | ||
106 | #else | ||
107 | #define LUA54_ONLY(a) "" | ||
108 | #endif | ||
109 | |||
110 | #if LUAJIT_FLAVOR() == 0 | ||
111 | #define PUC_LUA_ONLY(a) a | ||
112 | #define JIT_LUA_ONLY(a) "" | ||
113 | #else | ||
114 | #define PUC_LUA_ONLY(a) "" | ||
115 | #define JIT_LUA_ONLY(a) a | ||
116 | #endif | ||