Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/RequireByString.test.cpp
2723 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/Common.h"
3
#include "Luau/Config.h"
4
5
#include "ScopedFlags.h"
6
#include "lua.h"
7
#include "lualib.h"
8
9
#include "Luau/Repl.h"
10
#include "Luau/ReplRequirer.h"
11
#include "Luau/Require.h"
12
#include "Luau/FileUtils.h"
13
14
#include "doctest.h"
15
16
#include <algorithm>
17
#include <cstring>
18
#include <initializer_list>
19
#include <memory>
20
#include <optional>
21
#include <string>
22
#include <tuple>
23
#include <utility>
24
#include <vector>
25
26
#if __APPLE__
27
#include <TargetConditionals.h>
28
#if TARGET_OS_IPHONE
29
#include <CoreFoundation/CoreFoundation.h>
30
31
std::optional<std::string> getResourcePath0()
32
{
33
CFBundleRef mainBundle = CFBundleGetMainBundle();
34
if (mainBundle == NULL)
35
{
36
return std::nullopt;
37
}
38
CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
39
if (mainBundleURL == NULL)
40
{
41
CFRelease(mainBundle);
42
return std::nullopt;
43
}
44
45
char pathBuffer[PATH_MAX];
46
if (!CFURLGetFileSystemRepresentation(mainBundleURL, true, (UInt8*)pathBuffer, PATH_MAX))
47
{
48
CFRelease(mainBundleURL);
49
CFRelease(mainBundle);
50
return std::nullopt;
51
}
52
53
CFRelease(mainBundleURL);
54
CFRelease(mainBundle);
55
return std::string(pathBuffer);
56
}
57
58
std::optional<std::string> getResourcePath()
59
{
60
static std::optional<std::string> path0 = getResourcePath0();
61
return path0;
62
}
63
#endif
64
#endif
65
66
class ReplWithPathFixture
67
{
68
public:
69
ReplWithPathFixture()
70
: luaState(luaL_newstate(), lua_close)
71
{
72
L = luaState.get();
73
setupState(L);
74
luaL_sandboxthread(L);
75
76
runCode(L, prettyPrintSource);
77
}
78
79
// Returns all of the output captured from the pretty printer
80
std::string getCapturedOutput()
81
{
82
lua_getglobal(L, "capturedoutput");
83
const char* str = lua_tolstring(L, -1, nullptr);
84
std::string result(str);
85
lua_pop(L, 1);
86
return result;
87
}
88
89
enum class PathType
90
{
91
Absolute,
92
Relative
93
};
94
95
std::string getLuauDirectory(PathType type)
96
{
97
std::string luauDirRel = ".";
98
std::string luauDirAbs;
99
100
#if TARGET_OS_IPHONE
101
std::optional<std::string> cwd0 = getCurrentWorkingDirectory();
102
std::optional<std::string> cwd = getResourcePath();
103
if (cwd && cwd0)
104
{
105
// when running in xcode cwd0 is "/", however that is not always the case
106
const auto& _res = *cwd;
107
const auto& _cwd = *cwd0;
108
if (_res.find(_cwd) == 0)
109
{
110
// we need relative path so we subtract cwd0 from cwd
111
luauDirRel = "./" + _res.substr(_cwd.length());
112
}
113
}
114
#else
115
std::optional<std::string> cwd = getCurrentWorkingDirectory();
116
#endif
117
118
REQUIRE_MESSAGE(cwd, "Error getting Luau path");
119
std::replace((*cwd).begin(), (*cwd).end(), '\\', '/');
120
luauDirAbs = *cwd;
121
122
for (int i = 0; i < 20; ++i)
123
{
124
bool engineTestDir = isDirectory(luauDirAbs + "/Client/Luau/tests");
125
bool luauTestDir = isDirectory(luauDirAbs + "/tests/require");
126
127
if (engineTestDir || luauTestDir)
128
{
129
if (engineTestDir)
130
{
131
luauDirRel += "/Client/Luau";
132
luauDirAbs += "/Client/Luau";
133
}
134
135
if (type == PathType::Relative)
136
return luauDirRel;
137
if (type == PathType::Absolute)
138
return luauDirAbs;
139
}
140
141
if (luauDirRel == ".")
142
luauDirRel = "..";
143
else
144
luauDirRel += "/..";
145
146
std::optional<std::string> parentPath = getParentPath(luauDirAbs);
147
REQUIRE_MESSAGE(parentPath, "Error getting Luau path");
148
luauDirAbs = *parentPath;
149
}
150
151
// Could not find the directory
152
REQUIRE_MESSAGE(false, "Error getting Luau path");
153
return {};
154
}
155
156
void runProtectedRequire(const std::string& path)
157
{
158
runCode(L, "return pcall(function() return require(\"" + path + "\") end)");
159
}
160
161
void assertOutputContainsAll(const std::initializer_list<std::string>& list)
162
{
163
const std::string capturedOutput = getCapturedOutput();
164
for (const std::string& elem : list)
165
{
166
CHECK_MESSAGE(capturedOutput.find(elem) != std::string::npos, "Captured output: ", capturedOutput);
167
}
168
}
169
170
lua_State* L;
171
172
private:
173
std::unique_ptr<lua_State, void (*)(lua_State*)> luaState;
174
175
// This is a simplistic and incomplete pretty printer.
176
// It is included here to test that the pretty printer hook is being called.
177
// More elaborate tests to ensure correct output can be added if we introduce
178
// a more feature rich pretty printer.
179
std::string prettyPrintSource = R"(
180
-- Accumulate pretty printer output in `capturedoutput`
181
capturedoutput = ""
182
183
function arraytostring(arr)
184
local strings = {}
185
table.foreachi(arr, function(k,v) table.insert(strings, pptostring(v)) end )
186
return "{" .. table.concat(strings, ", ") .. "}"
187
end
188
189
function pptostring(x)
190
if type(x) == "table" then
191
-- Just assume array-like tables for now.
192
return arraytostring(x)
193
elseif type(x) == "string" then
194
return '"' .. x .. '"'
195
else
196
return tostring(x)
197
end
198
end
199
200
-- Note: Instead of calling print, the pretty printer just stores the output
201
-- in `capturedoutput` so we can check for the correct results.
202
function _PRETTYPRINT(...)
203
local args = table.pack(...)
204
local strings = {}
205
for i=1, args.n do
206
local item = args[i]
207
local str = pptostring(item, customoptions)
208
if i == 1 then
209
capturedoutput = capturedoutput .. str
210
else
211
capturedoutput = capturedoutput .. "\t" .. str
212
end
213
end
214
end
215
)";
216
};
217
218
TEST_SUITE_BEGIN("RequireByStringTests");
219
220
TEST_CASE("PathResolution")
221
{
222
#ifdef _WIN32
223
std::string prefix = "C:/";
224
#else
225
std::string prefix = "/";
226
#endif
227
228
// tuple format: {inputPath, inputBaseFilePath, expected}
229
std::vector<std::tuple<std::string, std::string, std::string>> tests = {
230
// 1. Basic path resolution
231
// a. Relative to a relative path that begins with './'
232
{"./dep", "./src/modules/module.luau", "./src/modules/dep"},
233
{"../dep", "./src/modules/module.luau", "./src/dep"},
234
{"../../dep", "./src/modules/module.luau", "./dep"},
235
{"../../", "./src/modules/module.luau", "./"},
236
237
// b. Relative to a relative path that begins with '../'
238
{"./dep", "../src/modules/module.luau", "../src/modules/dep"},
239
{"../dep", "../src/modules/module.luau", "../src/dep"},
240
{"../../dep", "../src/modules/module.luau", "../dep"},
241
{"../../", "../src/modules/module.luau", "../"},
242
243
// c. Relative to an absolute path
244
{"./dep", prefix + "src/modules/module.luau", prefix + "src/modules/dep"},
245
{"../dep", prefix + "src/modules/module.luau", prefix + "src/dep"},
246
{"../../dep", prefix + "src/modules/module.luau", prefix + "dep"},
247
{"../../", prefix + "src/modules/module.luau", prefix},
248
249
250
// 2. Check behavior for extraneous ".."
251
// a. Relative paths retain '..' and append if needed
252
{"../../../", "./src/modules/module.luau", "../"},
253
{"../../../", "../src/modules/module.luau", "../../"},
254
255
// b. Absolute paths ignore '..' if already at root
256
{"../../../", prefix + "src/modules/module.luau", prefix},
257
};
258
259
for (const auto& [inputPath, inputBaseFilePath, expected] : tests)
260
{
261
std::optional<std::string> resolved = resolvePath(inputPath, inputBaseFilePath);
262
CHECK(resolved);
263
CHECK_EQ(resolved, expected);
264
}
265
}
266
267
TEST_CASE("PathNormalization")
268
{
269
#ifdef _WIN32
270
std::string prefix = "C:/";
271
#else
272
std::string prefix = "/";
273
#endif
274
275
// pair format: {input, expected}
276
std::vector<std::pair<std::string, std::string>> tests = {
277
// 1. Basic formatting checks
278
{"", "./"},
279
{".", "./"},
280
{"..", "../"},
281
{"a/relative/path", "./a/relative/path"},
282
283
284
// 2. Paths containing extraneous '.' and '/' symbols
285
{"./remove/extraneous/symbols/", "./remove/extraneous/symbols"},
286
{"./remove/extraneous//symbols", "./remove/extraneous/symbols"},
287
{"./remove/extraneous/symbols/.", "./remove/extraneous/symbols"},
288
{"./remove/extraneous/./symbols", "./remove/extraneous/symbols"},
289
290
{"../remove/extraneous/symbols/", "../remove/extraneous/symbols"},
291
{"../remove/extraneous//symbols", "../remove/extraneous/symbols"},
292
{"../remove/extraneous/symbols/.", "../remove/extraneous/symbols"},
293
{"../remove/extraneous/./symbols", "../remove/extraneous/symbols"},
294
295
{prefix + "remove/extraneous/symbols/", prefix + "remove/extraneous/symbols"},
296
{prefix + "remove/extraneous//symbols", prefix + "remove/extraneous/symbols"},
297
{prefix + "remove/extraneous/symbols/.", prefix + "remove/extraneous/symbols"},
298
{prefix + "remove/extraneous/./symbols", prefix + "remove/extraneous/symbols"},
299
300
301
// 3. Paths containing '..'
302
// a. '..' removes the erasable component before it
303
{"./remove/me/..", "./remove"},
304
{"./remove/me/../", "./remove"},
305
306
{"../remove/me/..", "../remove"},
307
{"../remove/me/../", "../remove"},
308
309
{prefix + "remove/me/..", prefix + "remove"},
310
{prefix + "remove/me/../", prefix + "remove"},
311
312
// b. '..' stays if path is relative and component is non-erasable
313
{"./..", "../"},
314
{"./../", "../"},
315
316
{"../..", "../../"},
317
{"../../", "../../"},
318
319
// c. '..' disappears if path is absolute and component is non-erasable
320
{prefix + "..", prefix},
321
};
322
323
for (const auto& [input, expected] : tests)
324
{
325
CHECK_EQ(normalizePath(input), expected);
326
}
327
}
328
329
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePath")
330
{
331
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency";
332
runProtectedRequire(path);
333
assertOutputContainsAll({"true", "result from dependency"});
334
}
335
336
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSimpleRelativePathWithinPcall")
337
{
338
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency";
339
runCode(L, "return pcall(require, \"" + path + "\")");
340
assertOutputContainsAll({"true", "result from dependency"});
341
}
342
343
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireRelativeToRequiringFile")
344
{
345
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
346
runProtectedRequire(path);
347
assertOutputContainsAll({"true", "result from dependency", "required into module"});
348
}
349
350
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireLua")
351
{
352
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua_dependency";
353
runProtectedRequire(path);
354
assertOutputContainsAll({"true", "result from lua_dependency"});
355
}
356
357
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLuau")
358
{
359
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/luau";
360
runProtectedRequire(path);
361
assertOutputContainsAll({"true", "result from init.luau"});
362
}
363
364
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLua")
365
{
366
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua";
367
runProtectedRequire(path);
368
assertOutputContainsAll({"true", "result from init.lua"});
369
}
370
371
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSubmoduleUsingSelfIndirectly")
372
{
373
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested_module_requirer";
374
runProtectedRequire(path);
375
assertOutputContainsAll({"true", "result from submodule"});
376
}
377
378
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSubmoduleUsingSelfDirectly")
379
{
380
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested";
381
runProtectedRequire(path);
382
assertOutputContainsAll({"true", "result from submodule"});
383
}
384
385
TEST_CASE_FIXTURE(ReplWithPathFixture, "CannotRequireInitLuauDirectly")
386
{
387
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested/init";
388
runProtectedRequire(path);
389
assertOutputContainsAll({"false", "could not resolve child component \"init\""});
390
}
391
392
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireNestedInits")
393
{
394
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested_inits_requirer";
395
runProtectedRequire(path);
396
assertOutputContainsAll({"true", "result from nested_inits/init", "required into module"});
397
}
398
399
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity")
400
{
401
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_file_requirer";
402
403
runProtectedRequire(ambiguousPath);
404
assertOutputContainsAll(
405
{"false", "error requiring module \"./ambiguous/file/dependency\": could not resolve child component \"dependency\" (ambiguous)"}
406
);
407
}
408
409
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithAmbiguityInAliasDiscovery")
410
{
411
char executable[] = "luau";
412
std::vector<std::string> paths = {
413
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/parent_ambiguity/folder/requirer.luau",
414
getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config/parent_ambiguity/folder/requirer.luau",
415
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau",
416
getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau",
417
};
418
419
for (const std::string& path : paths)
420
{
421
std::vector<char> pathStr(path.size() + 1);
422
strncpy(pathStr.data(), path.c_str(), path.size());
423
pathStr[path.size()] = '\0';
424
425
char* argv[2] = {executable, pathStr.data()};
426
CHECK_EQ(replMain(2, argv), 0);
427
}
428
}
429
430
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity")
431
{
432
std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_directory_requirer";
433
434
runProtectedRequire(ambiguousPath);
435
assertOutputContainsAll(
436
{"false", "error requiring module \"./ambiguous/directory/dependency\": could not resolve child component \"dependency\" (ambiguous)"}
437
);
438
}
439
440
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLuau")
441
{
442
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
443
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
444
445
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
446
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
447
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
448
449
runProtectedRequire(relativePath);
450
451
assertOutputContainsAll({"true", "result from dependency", "required into module"});
452
453
// Check cache for the absolute path as a cache key
454
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
455
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
456
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
457
}
458
459
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireLua")
460
{
461
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua_dependency";
462
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/lua_dependency";
463
464
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
465
lua_getfield(L, -1, (absolutePath + ".luau").c_str());
466
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
467
468
runProtectedRequire(relativePath);
469
470
assertOutputContainsAll({"true", "result from lua_dependency"});
471
472
// Check cache for the absolute path as a cache key
473
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
474
lua_getfield(L, -1, (absolutePath + ".lua").c_str());
475
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
476
}
477
478
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireInitLuau")
479
{
480
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/luau";
481
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/luau";
482
483
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
484
lua_getfield(L, -1, (absolutePath + "/init.luau").c_str());
485
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
486
487
runProtectedRequire(relativePath);
488
489
assertOutputContainsAll({"true", "result from init.luau"});
490
491
// Check cache for the absolute path as a cache key
492
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
493
lua_getfield(L, -1, (absolutePath + "/init.luau").c_str());
494
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
495
}
496
497
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCacheAfterRequireInitLua")
498
{
499
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/lua";
500
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/lua";
501
502
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
503
lua_getfield(L, -1, (absolutePath + "/init.lua").c_str());
504
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
505
506
runProtectedRequire(relativePath);
507
508
assertOutputContainsAll({"true", "result from init.lua"});
509
510
// Check cache for the absolute path as a cache key
511
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
512
lua_getfield(L, -1, (absolutePath + "/init.lua").c_str());
513
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
514
}
515
516
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCachedResult")
517
{
518
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/validate_cache";
519
runProtectedRequire(relativePath);
520
assertOutputContainsAll({"true"});
521
}
522
523
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCacheEntry")
524
{
525
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
526
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
527
std::string cacheKey = absolutePath + ".luau";
528
529
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
530
lua_getfield(L, -1, cacheKey.c_str());
531
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
532
533
runProtectedRequire(relativePath);
534
535
assertOutputContainsAll({"true", "result from dependency", "required into module"});
536
537
// Check cache for the absolute path as a cache key
538
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
539
lua_getfield(L, -1, cacheKey.c_str());
540
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
541
542
lua_pushcfunction(L, luarequire_clearcacheentry, nullptr);
543
lua_pushstring(L, cacheKey.c_str());
544
lua_call(L, 1, 0);
545
546
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
547
lua_getfield(L, -1, cacheKey.c_str());
548
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared");
549
}
550
551
TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckClearCache")
552
{
553
std::string relativePath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module";
554
std::string absolutePath = getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module";
555
std::string cacheKey = absolutePath + ".luau";
556
557
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
558
lua_getfield(L, -1, cacheKey.c_str());
559
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache already contained module result");
560
561
runProtectedRequire(relativePath);
562
563
assertOutputContainsAll({"true", "result from dependency", "required into module"});
564
565
// Check cache for the absolute path as a cache key
566
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
567
lua_getfield(L, -1, cacheKey.c_str());
568
REQUIRE_FALSE_MESSAGE(lua_isnil(L, -1), "Cache did not contain module result");
569
570
lua_pushcfunction(L, luarequire_clearcache, nullptr);
571
lua_call(L, 0, 0);
572
573
luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1);
574
lua_getfield(L, -1, cacheKey.c_str());
575
REQUIRE_MESSAGE(lua_isnil(L, -1), "Cache was not cleared");
576
}
577
578
TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule")
579
{
580
lua_pushcfunction(L, luarequire_registermodule, nullptr);
581
lua_pushstring(L, "@test/helloworld");
582
lua_newtable(L);
583
lua_pushstring(L, "hello");
584
lua_pushstring(L, "world");
585
lua_settable(L, -3);
586
lua_call(L, 2, 0);
587
588
runCode(L, "return require('@test/helloworld').hello == 'world'");
589
assertOutputContainsAll({"true"});
590
}
591
592
TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModuleCaseInsensitive")
593
{
594
lua_pushcfunction(L, luarequire_registermodule, nullptr);
595
lua_pushstring(L, "@test/helloworld");
596
lua_newtable(L);
597
lua_pushstring(L, "hello");
598
lua_pushstring(L, "world");
599
lua_settable(L, -3);
600
lua_call(L, 2, 0);
601
602
runCode(L, "return require('@TeSt/heLLoWoRld').hello == 'world'");
603
assertOutputContainsAll({"true"});
604
}
605
606
TEST_CASE_FIXTURE(ReplWithPathFixture, "ProxyRequire")
607
{
608
luarequire_pushproxyrequire(L, requireConfigInit, createCliRequireContext(L));
609
lua_setglobal(L, "proxyrequire");
610
611
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/proxy_requirer";
612
runProtectedRequire(path);
613
assertOutputContainsAll({"true", "result from dependency", "required into proxy_requirer"});
614
}
615
616
TEST_CASE_FIXTURE(ReplWithPathFixture, "LoadStringRelative")
617
{
618
runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)");
619
assertOutputContainsAll({"false", "require is not supported in this context"});
620
}
621
622
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAbsolutePath")
623
{
624
std::string absolutePath = "/an/absolute/path";
625
626
runProtectedRequire(absolutePath);
627
assertOutputContainsAll({"false", "require path must start with a valid prefix: ./, ../, or @"});
628
}
629
630
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireUnprefixedPath")
631
{
632
std::string path = "an/unprefixed/path";
633
runProtectedRequire(path);
634
assertOutputContainsAll({"false", "require path must start with a valid prefix: ./, ../, or @"});
635
}
636
637
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias")
638
{
639
{
640
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/alias_requirer";
641
runProtectedRequire(path);
642
assertOutputContainsAll({"true", "result from dependency"});
643
}
644
{
645
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/alias_requirer";
646
runProtectedRequire(path);
647
assertOutputContainsAll({"true", "result from dependency"});
648
}
649
}
650
651
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithParentAlias")
652
{
653
{
654
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/parent_alias_requirer";
655
runProtectedRequire(path);
656
assertOutputContainsAll({"true", "result from other_dependency"});
657
}
658
{
659
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/parent_alias_requirer";
660
runProtectedRequire(path);
661
assertOutputContainsAll({"true", "result from other_dependency"});
662
}
663
}
664
665
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAliasPointingToDirectory")
666
{
667
{
668
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/directory_alias_requirer";
669
runProtectedRequire(path);
670
assertOutputContainsAll({"true", "result from subdirectory_dependency"});
671
}
672
{
673
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/directory_alias_requirer";
674
runProtectedRequire(path);
675
assertOutputContainsAll({"true", "result from subdirectory_dependency"});
676
}
677
}
678
679
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAliasThatDoesNotExist")
680
{
681
std::string nonExistentAlias = "@this.alias.does.not.exist";
682
683
runProtectedRequire(nonExistentAlias);
684
assertOutputContainsAll({"false", "@this.alias.does.not.exist is not a valid alias"});
685
}
686
687
TEST_CASE_FIXTURE(ReplWithPathFixture, "AliasHasIllegalFormat")
688
{
689
std::string illegalCharacter = "@@";
690
691
runProtectedRequire(illegalCharacter);
692
assertOutputContainsAll({"false", "@@ is not a valid alias"});
693
694
std::string pathAlias1 = "@.";
695
696
runProtectedRequire(pathAlias1);
697
assertOutputContainsAll({"false", ". is not a valid alias"});
698
699
700
std::string pathAlias2 = "@..";
701
702
runProtectedRequire(pathAlias2);
703
assertOutputContainsAll({"false", ".. is not a valid alias"});
704
705
std::string emptyAlias = "@";
706
707
runProtectedRequire(emptyAlias);
708
assertOutputContainsAll({"false", " is not a valid alias"});
709
}
710
711
TEST_CASE_FIXTURE(ReplWithPathFixture, "AliasNotParsedIfConfigsAmbiguous")
712
{
713
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/config_ambiguity/requirer";
714
runProtectedRequire(path);
715
assertOutputContainsAll({"false", "could not resolve alias \"dep\" (ambiguous configuration file)"});
716
}
717
718
TEST_CASE_FIXTURE(ReplWithPathFixture, "CannotRequireConfigLuau")
719
{
720
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/config_cannot_be_required/requirer";
721
runProtectedRequire(path);
722
assertOutputContainsAll({"false", "could not resolve child component \".config\""});
723
}
724
725
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFromLuauBinary")
726
{
727
char executable[] = "luau";
728
std::vector<std::string> paths = {
729
getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency.luau",
730
getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/dependency.luau",
731
getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module.luau",
732
getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module.luau",
733
getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested/init.luau",
734
getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/nested/init.luau",
735
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/submodule/init.luau",
736
getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config/src/submodule/init.luau",
737
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/submodule/init.luau",
738
getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config_luau/src/submodule/init.luau",
739
};
740
741
for (const std::string& path : paths)
742
{
743
std::vector<char> pathStr(path.size() + 1);
744
strncpy(pathStr.data(), path.c_str(), path.size());
745
pathStr[path.size()] = '\0';
746
747
char* argv[2] = {executable, pathStr.data()};
748
CHECK_EQ(replMain(2, argv), 0);
749
}
750
}
751
752
TEST_CASE("ParseAliases")
753
{
754
std::string configJson = R"({
755
"aliases": {
756
"MyAlias": "/my/alias/path",
757
}
758
})";
759
760
Luau::Config config;
761
762
Luau::ConfigOptions::AliasOptions aliasOptions;
763
aliasOptions.configLocation = "/default/location";
764
aliasOptions.overwriteAliases = true;
765
766
Luau::ConfigOptions options{false, aliasOptions};
767
768
std::optional<std::string> error = Luau::parseConfig(configJson, config, options);
769
REQUIRE(!error);
770
771
auto checkContents = [](Luau::Config& config) -> void
772
{
773
CHECK(config.aliases.size() == 1);
774
REQUIRE(config.aliases.contains("myalias"));
775
776
Luau::Config::AliasInfo& aliasInfo = config.aliases["myalias"];
777
CHECK(aliasInfo.value == "/my/alias/path");
778
CHECK(aliasInfo.originalCase == "MyAlias");
779
};
780
781
checkContents(config);
782
783
// Ensure that copied Configs retain the same information
784
Luau::Config copyConstructedConfig = config;
785
checkContents(copyConstructedConfig);
786
787
Luau::Config copyAssignedConfig;
788
copyAssignedConfig = config;
789
checkContents(copyAssignedConfig);
790
}
791
792
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireBoolean")
793
{
794
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/boolean";
795
runProtectedRequire(path);
796
assertOutputContainsAll({"true", "false"});
797
}
798
799
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireBuffer")
800
{
801
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/buffer";
802
runProtectedRequire(path);
803
assertOutputContainsAll({"true", "buffer"});
804
}
805
806
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFunction")
807
{
808
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/function";
809
runProtectedRequire(path);
810
assertOutputContainsAll({"true", "function"});
811
}
812
813
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireNil")
814
{
815
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/nil";
816
runProtectedRequire(path);
817
assertOutputContainsAll({"true", "nil"});
818
}
819
820
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireNumber")
821
{
822
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/number";
823
runProtectedRequire(path);
824
assertOutputContainsAll({"true", "12345"});
825
}
826
827
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireString")
828
{
829
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/string";
830
runProtectedRequire(path);
831
assertOutputContainsAll({"true", "\"foo\""});
832
}
833
834
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireTable")
835
{
836
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/table";
837
runProtectedRequire(path);
838
assertOutputContainsAll({"true", "{\"foo\", \"bar\"}"});
839
}
840
841
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireThread")
842
{
843
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/thread";
844
runProtectedRequire(path);
845
assertOutputContainsAll({"true", "thread"});
846
}
847
848
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireUserdata")
849
{
850
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/userdata";
851
runProtectedRequire(path);
852
assertOutputContainsAll({"true", "userdata"});
853
}
854
855
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireVector")
856
{
857
std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/vector";
858
runProtectedRequire(path);
859
assertOutputContainsAll({"true", "1, 2, 3"});
860
}
861
862
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireChainedAliasesSuccess")
863
{
864
{
865
std::string path =
866
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/chained_aliases/subdirectory/successful_requirer";
867
runProtectedRequire(path);
868
assertOutputContainsAll({"true", "result from inner_dependency", "result from outer_dependency"});
869
}
870
{
871
std::string path =
872
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/chained_aliases/subdirectory/successful_requirer";
873
runProtectedRequire(path);
874
assertOutputContainsAll({"true", "result from inner_dependency", "result from outer_dependency"});
875
}
876
}
877
878
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireChainedAliasesFailureCyclic")
879
{
880
{
881
std::string path =
882
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/chained_aliases/subdirectory/failing_requirer_cyclic";
883
runProtectedRequire(path);
884
assertOutputContainsAll(
885
{"false", "error requiring module \"@cyclicentry\": detected alias cycle (@cyclic1 -> @cyclic2 -> @cyclic3 -> @cyclic1)"}
886
);
887
}
888
{
889
std::string path = getLuauDirectory(PathType::Relative) +
890
"/tests/require/config_tests/with_config_luau/chained_aliases/subdirectory/failing_requirer_cyclic";
891
runProtectedRequire(path);
892
assertOutputContainsAll(
893
{"false", "error requiring module \"@cyclicentry\": detected alias cycle (@cyclic1 -> @cyclic2 -> @cyclic3 -> @cyclic1)"}
894
);
895
}
896
}
897
898
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireChainedAliasesFailureMissing")
899
{
900
{
901
std::string path =
902
getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/chained_aliases/subdirectory/failing_requirer_missing";
903
runProtectedRequire(path);
904
assertOutputContainsAll({"false", "error requiring module \"@brokenchain\": @missing is not a valid alias"});
905
}
906
{
907
std::string path = getLuauDirectory(PathType::Relative) +
908
"/tests/require/config_tests/with_config_luau/chained_aliases/subdirectory/failing_requirer_missing";
909
runProtectedRequire(path);
910
assertOutputContainsAll({"false", "error requiring module \"@brokenchain\": @missing is not a valid alias"});
911
}
912
}
913
914
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireChainedAliasesFailureDependOnInnerAlias")
915
{
916
{
917
std::string path = getLuauDirectory(PathType::Relative) +
918
"/tests/require/config_tests/with_config/chained_aliases/subdirectory/failing_requirer_inner_dependency";
919
runProtectedRequire(path);
920
assertOutputContainsAll({"false", "error requiring module \"@dependoninner\": @passthroughinner is not a valid alias"});
921
}
922
{
923
std::string path = getLuauDirectory(PathType::Relative) +
924
"/tests/require/config_tests/with_config_luau/chained_aliases/subdirectory/failing_requirer_inner_dependency";
925
runProtectedRequire(path);
926
assertOutputContainsAll({"false", "error requiring module \"@dependoninner\": @passthroughinner is not a valid alias"});
927
}
928
}
929
930
TEST_SUITE_END();
931
932