aboutsummaryrefslogtreecommitdiff
path: root/unit_tests
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-12-13 17:22:17 +0100
committerBenoit Germain <benoit.germain@ubisoft.com>2024-12-13 17:22:17 +0100
commitdc7a2bc3a9c8316e17902493b832ca117805d29f (patch)
tree3c1a03edf586869119ef0de03631f6f603650720 /unit_tests
parent7500a80fc06c5311c46df8f1761f25ae67277fc4 (diff)
downloadlanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.tar.gz
lanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.tar.bz2
lanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.zip
Append all unit tests to depot
Diffstat (limited to 'unit_tests')
-rw-r--r--unit_tests/UnitTests.vcxproj354
-rw-r--r--unit_tests/UnitTests.vcxproj.filters99
-rw-r--r--unit_tests/_pch.cpp4
-rw-r--r--unit_tests/_pch.hpp23
-rw-r--r--unit_tests/deep_tests.cpp101
-rw-r--r--unit_tests/embedded_tests.cpp140
-rw-r--r--unit_tests/init_and_shutdown.cpp793
-rw-r--r--unit_tests/lane_tests.cpp209
-rw-r--r--unit_tests/legacy_tests.cpp79
-rw-r--r--unit_tests/linda_tests.cpp342
-rw-r--r--unit_tests/scripts/_assert.lua321
-rw-r--r--unit_tests/scripts/_utils.lua75
-rw-r--r--unit_tests/scripts/coro/basics.lua94
-rw-r--r--unit_tests/scripts/coro/error_handling.lua64
-rw-r--r--unit_tests/scripts/lane/cooperative_shutdown.lua45
-rw-r--r--unit_tests/scripts/lane/stdlib_naming.lua87
-rw-r--r--unit_tests/scripts/lane/tasking_basic.lua74
-rw-r--r--unit_tests/scripts/lane/tasking_cancelling.lua122
-rw-r--r--unit_tests/scripts/lane/tasking_comms_criss_cross.lua53
-rw-r--r--unit_tests/scripts/lane/tasking_communications.lua149
-rw-r--r--unit_tests/scripts/lane/tasking_error.lua49
-rw-r--r--unit_tests/scripts/lane/tasking_join_test.lua65
-rw-r--r--unit_tests/scripts/lane/tasking_send_receive_code.lua91
-rw-r--r--unit_tests/scripts/lane/uncooperative_shutdown.lua24
-rw-r--r--unit_tests/scripts/linda/multiple_keepers.lua26
-rw-r--r--unit_tests/scripts/linda/send_receive.lua46
-rw-r--r--unit_tests/scripts/linda/send_registered_userdata.lua5
-rw-r--r--unit_tests/shared.cpp360
-rw-r--r--unit_tests/shared.h116
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
11extern "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
11class 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
27TEST_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
44TEST_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
80TEST_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
9namespace
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
26class 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
83TEST_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
111TEST_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
6TEST(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
66class 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
76TEST_F(Configure, AllocatorFalse)
77{
78 EXPECT_NE(L.doString("require 'lanes'.configure{allocator = false}"), LuaError::OK);
79}
80
81// #################################################################################################
82
83TEST_F(Configure, AllocatorTrue)
84{
85 EXPECT_NE(L.doString("require 'lanes'.configure{allocator = true}"), LuaError::OK);
86}
87
88// #################################################################################################
89
90TEST_F(Configure, AllocatorNumber)
91{
92 EXPECT_NE(L.doString("require 'lanes'.configure{allocator = 33}"), LuaError::OK);
93}
94
95// #################################################################################################
96
97TEST_F(Configure, AllocatorTable)
98{
99 EXPECT_NE(L.doString("require 'lanes'.configure{allocator = {}}"), LuaError::OK);
100}
101
102// #################################################################################################
103
104TEST_F(Configure, AllocatorLuaFunction)
105{
106 EXPECT_NE(L.doString("require 'lanes'.configure{allocator = function() return {}, 12, 'yoy' end}"), LuaError::OK);
107}
108
109// #################################################################################################
110
111TEST_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
119TEST_F(Configure, AllocatorTypo)
120{
121 // oops, a typo
122 EXPECT_NE(L.doString("require 'lanes'.configure{allocator = 'Protected'}"), LuaError::OK);
123}
124
125// #################################################################################################
126
127TEST_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
135TEST_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
150TEST_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
164TEST_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
180TEST_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
199TEST_F(Configure, InternalAllocatorFalse)
200{
201 EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = false}"), LuaError::OK);
202}
203
204// #################################################################################################
205
206TEST_F(Configure, InternalAllocatorTrue)
207{
208 EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = true}"), LuaError::OK);
209}
210
211// #################################################################################################
212
213TEST_F(Configure, InternalAllocatorTable)
214{
215 EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = {}}"), LuaError::OK);
216}
217
218// #################################################################################################
219
220TEST_F(Configure, InternalAllocatorFunction)
221{
222 EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = function() end}"), LuaError::OK);
223}
224
225// #################################################################################################
226
227TEST_F(Configure, InternalAllocatorString)
228{
229 EXPECT_NE(L.doString("require 'lanes'.configure{internal_allocator = 'gluh'}"), LuaError::OK);
230}
231
232// #################################################################################################
233
234TEST_F(Configure, InternalAllocatorLibc)
235{
236 EXPECT_EQ(L.doString("require 'lanes'.configure{internal_allocator = 'libc'}"), LuaError::OK);
237}
238
239// #################################################################################################
240
241TEST_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
250TEST_F(Configure, KeepersGcThresholdTable)
251{
252 EXPECT_NE(L.doString("require 'lanes'.configure{keepers_gc_threshold = {}}"), LuaError::OK);
253}
254
255// #################################################################################################
256
257TEST_F(Configure, KeepersGcThresholdString)
258{
259 EXPECT_NE(L.doString("require 'lanes'.configure{keepers_gc_threshold = 'gluh'}"), LuaError::OK);
260}
261
262// #################################################################################################
263
264TEST_F(Configure, KeepersGcThresholdNegative)
265{
266 EXPECT_EQ(L.doString("require 'lanes'.configure{keepers_gc_threshold = -1}"), LuaError::OK);
267}
268
269// #################################################################################################
270
271TEST_F(Configure, KeepersGcThresholdZero)
272{
273 EXPECT_EQ(L.doString("require 'lanes'.configure{keepers_gc_threshold = 0}"), LuaError::OK);
274}
275
276// #################################################################################################
277
278TEST_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
287TEST_F(Configure, NbUserKeepersTable)
288{
289 EXPECT_NE(L.doString("require 'lanes'.configure{nb_user_keepers = {}}"), LuaError::OK);
290}
291
292// #################################################################################################
293
294TEST_F(Configure, NbUserKeepersString)
295{
296 EXPECT_NE(L.doString("require 'lanes'.configure{nb_user_keepers = 'gluh'}"), LuaError::OK);
297}
298
299// #################################################################################################
300
301TEST_F(Configure, NbUserKeepersNegative)
302{
303 EXPECT_NE(L.doString("require 'lanes'.configure{nb_user_keepers = -1}"), LuaError::OK);
304}
305
306// #################################################################################################
307
308TEST_F(Configure, NbUserKeepersZero)
309{
310 EXPECT_EQ(L.doString("require 'lanes'.configure{nb_user_keepers = 0}"), LuaError::OK);
311}
312
313// #################################################################################################
314
315TEST_F(Configure, NbUserKeepersHundred)
316{
317 EXPECT_EQ(L.doString("require 'lanes'.configure{nb_user_keepers = 100}"), LuaError::OK);
318}
319
320// #################################################################################################
321
322TEST_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
331TEST_F(Configure, OnStateCreateTable)
332{
333 EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = {}}"), LuaError::OK);
334}
335
336// #################################################################################################
337
338TEST_F(Configure, OnStateCreateString)
339{
340 EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = 'gluh'}"), LuaError::OK);
341}
342
343// #################################################################################################
344
345TEST_F(Configure, OnStateCreateNumber)
346{
347 EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = 1}"), LuaError::OK);
348}
349
350// #################################################################################################
351
352TEST_F(Configure, OnStateCreateFalse)
353{
354 EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = false}"), LuaError::OK);
355}
356
357// #################################################################################################
358
359TEST_F(Configure, OnStateCreateTrue)
360{
361 EXPECT_NE(L.doString("require 'lanes'.configure{on_state_create = true}"), LuaError::OK);
362}
363
364// #################################################################################################
365
366TEST_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
374TEST_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
386TEST_F(Configure, ShutdownTimeoutTable)
387{
388 EXPECT_NE(L.doString("require 'lanes'.configure{shutdown_timeout = {}}"), LuaError::OK);
389}
390
391// #################################################################################################
392
393TEST_F(Configure, ShutdownTimeoutString)
394{
395 EXPECT_NE(L.doString("require 'lanes'.configure{shutdown_timeout = 'gluh'}"), LuaError::OK);
396}
397
398// #################################################################################################
399
400TEST_F(Configure, ShutdownTimeoutNegative)
401{
402 EXPECT_NE(L.doString("require 'lanes'.configure{shutdown_timeout = -0.001}"), LuaError::OK);
403}
404
405// #################################################################################################
406
407TEST_F(Configure, ShutdownTimeoutZero)
408{
409 EXPECT_EQ(L.doString("require 'lanes'.configure{shutdown_timeout = 0}"), LuaError::OK);
410}
411
412// #################################################################################################
413
414TEST_F(Configure, ShutdownTimeoutOne)
415{
416 EXPECT_EQ(L.doString("require 'lanes'.configure{shutdown_timeout = 1}"), LuaError::OK);
417}
418
419// #################################################################################################
420
421TEST_F(Configure, ShutdownTimeoutHour)
422{
423 EXPECT_EQ(L.doString("require 'lanes'.configure{shutdown_timeout = 3600}"), LuaError::OK);
424}
425
426// #################################################################################################
427
428TEST_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
437TEST_F(Configure, StripFunctionsTable)
438{
439 EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = {}}"), LuaError::OK);
440}
441
442// #################################################################################################
443
444TEST_F(Configure, StripFunctionsString)
445{
446 EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = 'gluh'}"), LuaError::OK);
447}
448
449// #################################################################################################
450
451TEST_F(Configure, StripFunctionsNumber)
452{
453 EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = 1}"), LuaError::OK);
454}
455
456// #################################################################################################
457
458TEST_F(Configure, StripFunctionsFunction)
459{
460 EXPECT_NE(L.doString("require 'lanes'.configure{strip_functions = print}"), LuaError::OK);
461}
462
463// #################################################################################################
464
465TEST_F(Configure, StripFunctionsFalse)
466{
467 EXPECT_EQ(L.doString("require 'lanes'.configure{strip_functions = false}"), LuaError::OK);
468}
469
470// #################################################################################################
471
472TEST_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
481TEST_F(Configure, TrackLanesTable)
482{
483 EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = {}}"), LuaError::OK);
484}
485
486// #################################################################################################
487
488TEST_F(Configure, TrackLanesString)
489{
490 EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = 'gluh'}"), LuaError::OK);
491}
492
493// #################################################################################################
494
495TEST_F(Configure, TrackLanesNumber)
496{
497 EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = 1}"), LuaError::OK);
498}
499
500// #################################################################################################
501
502TEST_F(Configure, TrackLanesFunction)
503{
504 EXPECT_NE(L.doString("require 'lanes'.configure{track_lanes = print}"), LuaError::OK);
505}
506
507// #################################################################################################
508
509TEST_F(Configure, TrackLanesFalse)
510{
511 EXPECT_EQ(L.doString("require 'lanes'.configure{track_lanes = false}"), LuaError::OK);
512}
513
514// #################################################################################################
515
516TEST_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
525TEST_F(Configure, VerboseErrorsTable)
526{
527 EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = {}}"), LuaError::OK);
528}
529
530// #################################################################################################
531
532TEST_F(Configure, VerboseErrorsString)
533{
534 EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = 'gluh'}"), LuaError::OK);
535}
536
537// #################################################################################################
538
539TEST_F(Configure, VerboseErrorsNumber)
540{
541 EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = 1}"), LuaError::OK);
542}
543
544// #################################################################################################
545
546TEST_F(Configure, VerboseErrorsFunction)
547{
548 EXPECT_NE(L.doString("require 'lanes'.configure{verbose_errors = print}"), LuaError::OK);
549}
550
551// #################################################################################################
552
553TEST_F(Configure, VerboseErrorsFalse)
554{
555 EXPECT_EQ(L.doString("require 'lanes'.configure{verbose_errors = false}"), LuaError::OK);
556}
557
558// #################################################################################################
559
560TEST_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
569TEST_F(Configure, WithTimersTable)
570{
571 EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = {}}"), LuaError::OK);
572}
573
574// #################################################################################################
575
576TEST_F(Configure, WithTimersString)
577{
578 EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = 'gluh'}"), LuaError::OK);
579}
580
581// #################################################################################################
582
583TEST_F(Configure, WithTimersNumber)
584{
585 EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = 1}"), LuaError::OK);
586}
587
588// #################################################################################################
589
590TEST_F(Configure, WithTimersFunction)
591{
592 EXPECT_NE(L.doString("require 'lanes'.configure{with_timers = print}"), LuaError::OK);
593}
594
595// #################################################################################################
596
597TEST_F(Configure, WithTimersFalse)
598{
599 EXPECT_EQ(L.doString("require 'lanes'.configure{with_timers = false}"), LuaError::OK);
600}
601
602// #################################################################################################
603
604TEST_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
613TEST_F(Configure, UnknownSettingTable)
614{
615 EXPECT_NE(L.doString("require 'lanes'.configure{[{}] = {}}"), LuaError::OK);
616}
617
618// #################################################################################################
619
620TEST_F(Configure, UnknownSettingBool)
621{
622 EXPECT_NE(L.doString("require 'lanes'.configure{[true] = 'gluh'}"), LuaError::OK);
623}
624
625// #################################################################################################
626
627TEST_F(Configure, UnknownSettingFunction)
628{
629 EXPECT_NE(L.doString("require 'lanes'.configure{[function() end] = 1}"), LuaError::OK);
630}
631
632// #################################################################################################
633
634TEST_F(Configure, UnknownSettingNumber)
635{
636 EXPECT_NE(L.doString("require 'lanes'.configure{[1] = function() end}"), LuaError::OK);
637}
638
639// #################################################################################################
640
641TEST_F(Configure, UnknownSettingString)
642{
643 EXPECT_NE(L.doString("require 'lanes'.configure{['gluh'] = false}"), LuaError::OK);
644}
645
646// #################################################################################################
647// #################################################################################################
648
649TEST(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
686namespace
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
700class 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
718TEST_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
729TEST_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
740TEST_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
7class 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
20TEST_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
98TEST_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
129class 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
143TEST_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
191INSTANTIATE_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
9class 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
27TEST_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
43INSTANTIATE_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
6class 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
17TEST_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
77TEST_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
90TEST_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
104TEST_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
119TEST_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
130TEST_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
137TEST_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
145TEST_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
162TEST_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
194TEST_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
203TEST_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
214TEST_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
235TEST_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
264TEST_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
282TEST_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
296TEST_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
309class 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
320TEST_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
333INSTANTIATE_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
50local 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
61assert( type(assert) == "function" ) -- system assert function
62assert( type(error) == "function")
63
64-----
65-- Integer range: INT_MIN..INT_MAX
66--
67local function try_maxint( n )
68 return (n > n-1) and n-1 -- false when outside the integer range
69end
70
71local 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
77local INT_MIN= -(INT_MAX+1)
78
79
80---=== assert.*() ===---
81
82local at_msg= "type assertion error" -- TBD: better messages, about that exact situation
83local av_msg= "value assertion error"
84
85-- void= _assert( val [, msg_str [, lev_uint]] )
86--
87local 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
93end
94
95-- Note: following code uses the _new_ 'assert()' by purpose, since it provides
96-- a level override (original doesn't)
97--
98local function assert_v( v0 )
99 return function(v,msg)
100 _assert( v == v0, msg or av_msg, 2 )
101 return v
102 end
103end
104local function assert_t( str )
105 return function(v,msg)
106 _assert( type(v) == str, msg or at_msg, 2 )
107 return v
108 end
109end
110local 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
117end
118
119-- global base function assert() is replaced by this table
120assert=
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}
259setmetatable( assert, assert )
260
261assert.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--
270function 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
279end
280
281
282-----
283-- void= assert.failsnot( function [,err_msg_str] )
284--
285-- Similar to 'assert.fails' but expects the code to survive.
286--
287function 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
294end
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--
303function 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
311end
312
313
314-- Sanity check
315--
316assert( true )
317assert.fails( function() assert( false ) end )
318assert.fails( function() assert( nil ) end )
319
320
321return 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 @@
1local io = assert(io)
2local pairs = assert(pairs)
3local select = assert(select)
4local string_format = assert(string.format)
5local tostring = assert(tostring)
6local type = assert(type)
7
8local print_id = 0
9local 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
19end
20
21local MAKE_PRINT = function()
22 local _whence = lane_threadname and lane_threadname() or "main"
23 return function(...)
24 P(_whence, ...)
25 end
26end
27
28local tables_match
29
30-- true if 'a' is a subtable of 'b'
31--
32local 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
46end
47
48-- true when contents of 'a' and 'b' are identical
49--
50tables_match = function(a, b)
51 return subtable(a, b) and subtable(b, a)
52end
53
54local 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
69end
70
71return {
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 @@
1local lanes = require "lanes"
2
3local fixture = require "fixture"
4lanes.finally(fixture.throwing_finalizer)
5
6local utils = lanes.require "_utils"
7local PRINT = utils.MAKE_PRINT()
8
9if 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")
27end
28
29-- a lane coroutine that yields back what got in, one element at a time
30local 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!"
41end
42
43if 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)
58end
59
60-- the generator
61local coro_g = lanes.coro("*", {name = "auto"}, yielder)
62
63if 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!")
73end
74
75if 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")
94end
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 @@
1local lanes = require "lanes".configure{strip_functions = true}
2
3local fixture = require "fixture"
4lanes.finally(fixture.throwing_finalizer)
5
6local utils = lanes.require "_utils"
7local PRINT = utils.MAKE_PRINT()
8
9-- a lane coroutine that yields back what got in, one element at a time
10local 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!"
21end
22
23local 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)
39end
40
41if false then
42 force_error_test("minimal")
43end
44
45if false then
46 force_error_test("basic")
47end
48
49if false then
50 force_error_test("extended")
51end
52
53if 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)
64end
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 @@
1local lanes = require "lanes"
2
3-- launch lanes that cooperate properly with cancellation request
4
5local lane1 = function()
6 -- loop breaks on cancellation request
7 repeat
8 lanes.sleep(0)
9 until cancel_test()
10 print "lane1 cancelled"
11end
12
13local 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"
19end
20
21local 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
25end
26
27
28
29-- the generators
30local g1 = lanes.gen("*", lane1)
31local g2 = lanes.gen("*", lane2)
32local g3 = lanes.gen("*", lane3)
33
34-- launch lanes
35local h1 = g1()
36
37local linda = lanes.linda()
38local h2 = g2(linda)
39
40local h3 = g3()
41
42-- wait until they are both started
43repeat 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 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1
4
5local require_assert_result_1, require_assert_result_2 = require "_assert"
6print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7
8local utils = lanes.require "_utils"
9local PRINT = utils.MAKE_PRINT()
10
11local lanes_gen = assert(lanes.gen)
12local lanes_linda = assert(lanes.linda)
13
14-- ##################################################################################################
15-- ##################################################################################################
16-- ##################################################################################################
17
18local 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
27end
28
29local gc_cb = function(name_, status_)
30 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
31end
32
33-- ##################################################################################################
34-- ##################################################################################################
35-- ##################################################################################################
36
37PRINT("\n\n", "---=== Stdlib naming ===---", "\n\n")
38
39local 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
47end
48
49local 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
56end
57
58local 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
64end
65
66assert.fails(function() lanes_gen("xxx", {gc_cb = gc_cb}, io_os_f) end)
67
68local 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
81for _, 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])
84end
85
86PRINT("collectgarbage")
87collectgarbage()
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 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1
4
5local require_assert_result_1, require_assert_result_2 = require "_assert"
6print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7
8local utils = lanes.require "_utils"
9local PRINT = utils.MAKE_PRINT()
10
11local lanes_gen = assert(lanes.gen)
12local lanes_linda = assert(lanes.linda)
13
14-- ##################################################################################################
15-- ##################################################################################################
16-- ##################################################################################################
17
18local 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
30end
31
32local gc_cb = function(name_, status_)
33 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
34end
35
36-- ##################################################################################################
37-- ##################################################################################################
38-- ##################################################################################################
39
40PRINT("\n\n", "---=== Tasking (basic) ===---", "\n\n")
41
42local 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
47local lane1 = task_launch(100,200,3)
48assert.fails(function() print(lane1[lane1]) end) -- indexing the lane with anything other than a string or a number should fail
49lanes.sleep(0.1) -- give some time so that the lane can set its name
50assert(lane1:get_threadname() == "task(100,200,3)", "Lane name is " .. lane1:get_threadname())
51
52local lane2= task_launch(200,300,4)
53
54-- At this stage, states may be "pending", "running" or "done"
55
56local st1,st2= lane1.status, lane2.status
57PRINT(st1,st2)
58assert(st1=="pending" or st1=="running" or st1=="done")
59assert(st2=="pending" or st2=="running" or st2=="done")
60
61-- Accessing results ([1..N]) pends until they are available
62--
63PRINT("waiting...")
64local v1, v1_hey= lane1[1], lane1[2]
65local v2, v2_hey= lane2[1], lane2[2]
66
67PRINT(v1, v1_hey)
68assert(v1_hey == true)
69
70PRINT(v2, v2_hey)
71assert(v2_hey == true)
72
73assert(lane1.status == "done")
74assert(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 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1
4
5local require_assert_result_1, require_assert_result_2 = require "_assert"
6print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7
8local utils = lanes.require "_utils"
9local PRINT = utils.MAKE_PRINT()
10
11local lanes_gen = assert(lanes.gen)
12local lanes_linda = assert(lanes.linda)
13
14-- ##################################################################################################
15-- ##################################################################################################
16-- ##################################################################################################
17
18local 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
27end
28
29local gc_cb = function(name_, status_)
30 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
31end
32
33-- ##################################################################################################
34-- ##################################################################################################
35-- ##################################################################################################
36
37PRINT("\n\n", "---=== Tasking (cancelling) ===---", "\n\n")
38
39local task_launch2 = lanes_gen("", { globals={hey=true}, gc_cb = gc_cb}, task)
40
41local N=999999999
42local lane9= task_launch2(1,N,1) -- huuuuuuge...
43
44-- Wait until state changes "pending"->"running"
45--
46local st
47local t0= os.time()
48while 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
52end
53PRINT(" "..st)
54
55if st=="error" then
56 local _= lane9[0] -- propagate the error here
57end
58if st=="done" then
59 error("Looping to "..N.." was not long enough (cannot test cancellation)")
60end
61assert(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
64lane9:cancel(jit and "line" or "count", 100) -- 0 timeout, hook triggers cancelslation when reaching the specified count
65
66local t0= os.time()
67while 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
71end
72PRINT(" "..st)
73assert(st == "cancelled", "st is '" .. st .. "' instead of 'cancelled'")
74
75-- cancellation of lanes waiting on a linda
76local limited = lanes_linda("limited")
77assert.fails(function() limited:limit("key", -1) end)
78assert.failsnot(function() limited:limit("key", 1) end)
79-- [[################################################
80limited:send("key", "hello") -- saturate linda
81for k, v in pairs(limited:dump()) do
82 PRINT("limited[" .. tostring(k) .. "] = " .. tostring(v))
83end
84local 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
88end
89
90local wait_send_lane = lanes.gen("*", wait_send)()
91repeat until wait_send_lane.status == "waiting"
92print "wait_send_lane is waiting"
93wait_send_lane:cancel() -- hard cancel, 0 timeout
94repeat until wait_send_lane.status == "cancelled"
95print "wait_send_lane is cancelled"
96--################################################]]
97local 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
101end
102
103local wait_receive_lane = lanes.gen("*", wait_receive)()
104repeat until wait_receive_lane.status == "waiting"
105print "wait_receive_lane is waiting"
106wait_receive_lane:cancel() -- hard cancel, 0 timeout
107repeat until wait_receive_lane.status == "cancelled"
108print "wait_receive_lane is cancelled"
109--################################################]]
110local 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
114end
115
116local wait_receive_batched_lane = lanes.gen("*", wait_receive_batched)()
117repeat until wait_receive_batched_lane.status == "waiting"
118print "wait_receive_batched_lane is waiting"
119wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout
120repeat until wait_receive_batched_lane.status == "cancelled"
121print "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 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1
4
5local require_assert_result_1, require_assert_result_2 = require "_assert"
6print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7
8local utils = lanes.require "_utils"
9local PRINT = utils.MAKE_PRINT()
10
11local lanes_gen = assert(lanes.gen)
12local lanes_linda = assert(lanes.linda)
13
14-- ##################################################################################################
15-- ##################################################################################################
16-- ##################################################################################################
17
18local gc_cb = function(name_, status_)
19 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
20end
21
22-- ##################################################################################################
23-- ##################################################################################################
24-- ##################################################################################################
25
26PRINT("\n\n", "---=== Comms criss cross ===---", "\n\n")
27
28-- We make two identical lanes, which are using the same Linda channel.
29--
30local 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
45local linda= lanes_linda("criss cross")
46
47local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twisted comms
48
49local _= a[0],b[0] -- waits until they are both ready
50
51PRINT("collectgarbage")
52a, b = nil
53collectgarbage()
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 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1
4
5local require_assert_result_1, require_assert_result_2 = require "_assert"
6print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7
8local utils = lanes.require "_utils"
9local PRINT = utils.MAKE_PRINT()
10
11local lanes_gen = assert(lanes.gen)
12local lanes_linda = assert(lanes.linda)
13
14-- ##################################################################################################
15-- ##################################################################################################
16-- ##################################################################################################
17
18local 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
27end
28
29local gc_cb = function(name_, status_)
30 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
31end
32
33-- ##################################################################################################
34-- ##################################################################################################
35-- ##################################################################################################
36
37local tables_match = utils.tables_match
38
39local SLEEP = function(...)
40 local k, v = lanes.sleep(...)
41 assert(k == nil and v == "timeout")
42end
43
44PRINT("\n\n", "---=== Communications ===---", "\n\n")
45
46local function WR(...) io.stderr:write(...) end
47
48local 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")
73end
74
75local linda = lanes_linda("communications")
76assert(type(linda) == "userdata" and tostring(linda) == "Linda: communications")
77 --
78 -- ["->"] master -> slave
79 -- ["<-"] slave <- master
80
81WR "test linda:get/set..."
82linda:set("<->", "x", "y", "z")
83local b,x,y,z = linda:get("<->", 1)
84assert(b == 1 and x == "x" and y == nil and z == nil)
85local b,x,y,z = linda:get("<->", 2)
86assert(b == 2 and x == "x" and y == "y" and z == nil)
87local b,x,y,z = linda:get("<->", 3)
88assert(b == 3 and x == "x" and y == "y" and z == "z")
89local b,x,y,z,w = linda:get("<->", 4)
90assert(b == 3 and x == "x" and y == "y" and z == "z" and w == nil)
91local k, x = linda:receive("<->")
92assert(k == "<->" and x == "x")
93local k,y,z = linda:receive(linda.batched, "<->", 2)
94assert(k == "<->" and y == "y" and z == "z")
95linda:set("<->")
96local b,x,y,z,w = linda:get("<->", 4)
97assert(b == 0 and x == nil and y == nil and z == nil and w == nil)
98WR "ok\n"
99
100local function PEEK(...) return linda:get("<-", ...) end
101local function SEND(...) local _res, _err = linda:send("->", ...) assert(_res == true and _err == nil) end
102local function RECEIVE() local k,v = linda:receive(1, "<-") return v end
103
104local comms_lane = lanes_gen("io", {gc_cb = gc_cb, name = "auto"}, chunk)(linda) -- prepare & launch
105
106SEND(1); WR("main ", "1 sent\n")
107SEND(2); WR("main ", "2 sent\n")
108SEND(3); WR("main ", "3 sent\n")
109SEND(setmetatable({"should be ignored"},{__lanesconvert=lanes.null})); WR("main ", "__lanesconvert table sent\n")
110for i=1,40 do
111 WR "."
112 SLEEP(0.0001)
113 assert(PEEK() == 0) -- nothing coming in, yet
114end
115SEND(nil); WR("\nmain ", "nil sent\n")
116
117local a,b,c = RECEIVE(), RECEIVE(), RECEIVE()
118
119print("lane status: " .. comms_lane.status)
120if comms_lane.status == "error" then
121 print(comms_lane:join())
122else
123 WR("main ", tostring(a)..", "..tostring(b)..", "..tostring(c).." received\n")
124end
125
126assert(a==4 and b==5 and c==6)
127
128local aaa = RECEIVE(); WR("main ", aaa.." received\n")
129assert(aaa=='aaa')
130
131local null = RECEIVE(); WR(tostring(null).." received\n")
132assert(null==nil)
133
134local out_t = RECEIVE(); WR(type(out_t).." received\n")
135assert(tables_match(out_t, {'a','b','c',d=10}))
136
137assert(PEEK() == 0)
138SEND(4)
139
140local complex_table = RECEIVE(); WR(type(complex_table).." received\n")
141assert(complex_table[1] == complex_table[3] and complex_table[2] == complex_table[4])
142WR(table.concat({complex_table[1][1],complex_table[2][1],complex_table[3][1],complex_table[4][1]},", "))
143
144WR("collectgarbage")
145comms_lane = nil
146collectgarbage()
147-- wait
148WR("waiting 1s")
149SLEEP(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 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1
4
5local require_assert_result_1, require_assert_result_2 = require "_assert"
6print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7
8local utils = lanes.require "_utils"
9local PRINT = utils.MAKE_PRINT()
10
11local lanes_gen = assert(lanes.gen)
12local lanes_linda = assert(lanes.linda)
13
14-- ##################################################################################################
15-- ##################################################################################################
16-- ##################################################################################################
17
18local gc_cb = function(name_, status_)
19 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
20end
21
22PRINT("---=== Tasking (error) ===---", "\n\n")
23
24-- a lane that throws immediately the error value it received
25local g = lanes_gen("", {gc_cb = gc_cb}, error)
26local errmsg = "ERROR!"
27
28if 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))
36end
37
38if 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)
49end
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 @@
1local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
2print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
3local lanes = require_lanes_result_1
4
5local require_assert_result_1, require_assert_result_2 = require "_assert"
6print("require_assert_result:", require_assert_result_1, require_assert_result_2)
7
8local utils = lanes.require "_utils"
9local PRINT = utils.MAKE_PRINT()
10
11local lanes_gen = assert(lanes.gen)
12local lanes_linda = assert(lanes.linda)
13
14-- ##################################################################################################
15-- ##################################################################################################
16-- ##################################################################################################
17
18local gc_cb = function(name_, status_)
19 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
20end
21
22-- ##################################################################################################
23-- ##################################################################################################
24-- ##################################################################################################
25
26local SLEEP = function(...)
27 local k, v = lanes.sleep(...)
28 assert(k == nil and v == "timeout")
29end
30
31PRINT("---=== :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
37local 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)
47end)
48
49h= 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
51SLEEP(0.5)
52print("joining with '" .. h:get_threadname() .. "'")
53local a,b,c,d= h:join()
54if h.status == "error" then
55 print(h:get_threadname(), "error: " , a, b, c, d)
56else
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))
62end
63
64local nameof_type, nameof_name = lanes.nameof(print)
65PRINT("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 @@
1local config = { with_timers = false, strip_functions = false, internal_allocator = "libc"}
2local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure(config).configure()
3print("require_lanes_result:", require_lanes_result_1, require_lanes_result_2)
4local lanes = require_lanes_result_1
5
6local require_assert_result_1, require_assert_result_2 = require "_assert"
7print("require_assert_result:", require_assert_result_1, require_assert_result_2)
8
9local utils = lanes.require "_utils"
10local PRINT = utils.MAKE_PRINT()
11
12local lanes_gen = assert(lanes.gen)
13local lanes_linda = assert(lanes.linda)
14
15-- ##################################################################################################
16-- ##################################################################################################
17-- ##################################################################################################
18
19local gc_cb = function(name_, status_)
20 PRINT(" ---> lane '" .. name_ .. "' collected with status '" .. status_ .. "'")
21end
22
23-- ##################################################################################################
24-- ##################################################################################################
25-- ##################################################################################################
26
27PRINT("---=== Receive & send of code ===---", "\n")
28
29local upvalue = "123"
30local tostring = tostring
31
32local 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")
66end
67
68local linda = lanes_linda("auto")
69local t2= lanes_gen("debug,package,string,io", {gc_cb = gc_cb}, chunk2)(linda) -- prepare & launch
70linda:send("down", function(linda) linda:send("up", "ready!") end,
71 "ok")
72-- wait to see if the tiny function gets executed
73--
74local k,s= linda:receive(1, "up")
75if t2.status == "error" then
76 PRINT("t2 error: " , t2:join())
77 assert(false)
78end
79PRINT(s)
80assert(s=="ready!", s .. " is not 'ready!'")
81
82-- returns of the 'chunk2' itself
83--
84local k,f= linda:receive("up")
85assert(type(f)=="function")
86
87local s2= f()
88assert(s2==":)")
89
90local k,ok2= linda:receive("up")
91assert(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 @@
1if not package.preload.fixture then
2 error "can't be run outside of UnitTest framework"
3end
4local fixture = require "fixture"
5
6local lanes = require "lanes".configure{shutdown_timeout = 0.001, on_state_create = fixture.on_state_create}
7
8-- launch lanes that blocks forever
9local lane = function()
10 local fixture = require "fixture"
11 fixture.forever()
12end
13
14-- the generator
15local g1 = lanes.gen("*", lane)
16
17-- launch lane
18local h1 = g1()
19
20-- wait until the lane is running
21repeat until h1.status == "running"
22
23-- let the script end, Lanes should throw std::logic_error because the lane did not gracefully terminate
24lanes.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
2local require_lanes_result_1, require_lanes_result_2 = require "lanes".configure{nb_user_keepers = 3, keepers_gc_threshold = 500}
3local lanes = require_lanes_result_1
4
5local a = lanes.linda("A", 1)
6local b = lanes.linda("B", 2)
7local c = lanes.linda("C", 3)
8
9-- store each linda in the other 2
10do
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)
14end
15
16do
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)
20end
21
22do
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)
26end
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 @@
1local lanes = require "lanes"
2
3-- a newly created linda doesn't contain anything
4local l = lanes.linda()
5assert(l.null == lanes.null)
6assert(l:dump() == nil)
7
8-- read something with 0 timeout, should fail
9local k,v = l:receive(0, "k")
10assert(k == nil and v == 'timeout')
11
12-- send some value
13assert(l:send("k1", 99) == true)
14-- make sure the contents look as expected
15local t = l:dump()
16assert(type(t) == 'table')
17local tk1 = t.k1
18assert(type(tk1) == 'table')
19assert(tk1.first == 1 and tk1.count == 1 and tk1.limit == 'unlimited' and tk1.restrict == 'none')
20assert(#tk1.fifo == tk1.count, #tk1.fifo .. " ~= " .. tk1.count)
21assert(tk1.fifo[1] == 99, tk1.fifo[1] .. " ~= " .. 99)
22
23-- read the value, should succeed
24local k,v = l:receive("k1")
25assert(k == "k1" and v == 99)
26-- after reading, the data is no longer available (even if the key itself still exists within the linda)
27local t = l:dump()
28assert(type(t) == 'table')
29local tk1 = t.k1
30assert(type(tk1) == 'table')
31assert(tk1.first == 1 and tk1.count == 0 and tk1.limit == 'unlimited' and tk1.restrict == 'none')
32assert(#tk1.fifo == tk1.count and tk1.fifo[1] == nil)
33-- read again, should fail
34local k,v = l:receive(0, "k1")
35assert(k == nil and v == 'timeout')
36
37-- send null, read nil
38l:send("k", l.null)
39local k,v = l:receive(0, "k")
40assert(k == "k" and v == nil)
41
42-- using a deep userdata (such as another linda) as key, should work
43local l2 = lanes.linda()
44l:send(nil, l2, 99)
45local k,v = l:receive(0, l2)
46assert(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 @@
1local lanes = require 'lanes'.configure{with_timers = false}
2local l = lanes.linda'gleh'
3l:set('yo', io.stdin)
4local n, stdin_out = l:get('yo')
5assert(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
11LANES_API int luaopen_deep_test(lua_State* L_);
12
13namespace
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
101TEST(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
159LuaState::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
175void 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
192LuaError 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
213std::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
233LuaError 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
256LuaError 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
272LuaError 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
292LuaError 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
303TEST(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
329UnitTestRunner::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
346TEST_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
5class 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
53enum class TestType
54{
55 AssertNoLuaError,
56 AssertNoThrow,
57 AssertThrows,
58};
59
60struct FileRunnerParam
61{
62 std::string_view script;
63 TestType test;
64};
65
66class 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
89class 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