Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/Repl.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 "lua.h"
3
#include "lualib.h"
4
5
#include "Luau/Repl.h"
6
#include "ScopedFlags.h"
7
8
#include "doctest.h"
9
10
#include <iostream>
11
#include <memory>
12
#include <set>
13
#include <string>
14
#include <vector>
15
16
LUAU_FASTFLAG(LuauIntegerType)
17
18
struct Completion
19
{
20
std::string completion;
21
std::string display;
22
23
bool operator<(Completion const& other) const
24
{
25
return std::tie(completion, display) < std::tie(other.completion, other.display);
26
}
27
};
28
29
using CompletionSet = std::set<Completion>;
30
31
class ReplFixture
32
{
33
public:
34
ReplFixture()
35
: luaState(luaL_newstate(), lua_close)
36
{
37
L = luaState.get();
38
setupState(L);
39
luaL_sandboxthread(L);
40
41
std::string result = runCode(L, prettyPrintSource);
42
}
43
44
// Returns all of the output captured from the pretty printer
45
std::string getCapturedOutput()
46
{
47
lua_getglobal(L, "capturedoutput");
48
const char* str = lua_tolstring(L, -1, nullptr);
49
std::string result(str);
50
lua_pop(L, 1);
51
return result;
52
}
53
54
CompletionSet getCompletionSet(const char* inputPrefix)
55
{
56
CompletionSet result;
57
int top = lua_gettop(L);
58
getCompletions(
59
L,
60
inputPrefix,
61
[&result](const std::string& completion, const std::string& display)
62
{
63
result.insert(Completion{completion, display});
64
}
65
);
66
// Ensure that generating completions doesn't change the position of luau's stack top.
67
CHECK(top == lua_gettop(L));
68
69
return result;
70
}
71
72
bool checkCompletion(const CompletionSet& completions, const std::string& prefix, const std::string& expected)
73
{
74
std::string expectedDisplay(expected.substr(0, expected.find_first_of('(')));
75
Completion expectedCompletion{prefix + expected, expectedDisplay};
76
return completions.count(expectedCompletion) == 1;
77
}
78
79
lua_State* L;
80
81
private:
82
std::unique_ptr<lua_State, void (*)(lua_State*)> luaState;
83
84
// This is a simplistic and incomplete pretty printer.
85
// It is included here to test that the pretty printer hook is being called.
86
// More elaborate tests to ensure correct output can be added if we introduce
87
// a more feature rich pretty printer.
88
std::string prettyPrintSource = R"(
89
-- Accumulate pretty printer output in `capturedoutput`
90
capturedoutput = ""
91
92
function arraytostring(arr)
93
local strings = {}
94
table.foreachi(arr, function(k,v) table.insert(strings, pptostring(v)) end )
95
return "{" .. table.concat(strings, ", ") .. "}"
96
end
97
98
function pptostring(x)
99
if type(x) == "table" then
100
-- Just assume array-like tables for now.
101
return arraytostring(x)
102
elseif type(x) == "string" then
103
return '"' .. x .. '"'
104
else
105
return tostring(x)
106
end
107
end
108
109
-- Note: Instead of calling print, the pretty printer just stores the output
110
-- in `capturedoutput` so we can check for the correct results.
111
function _PRETTYPRINT(...)
112
local args = table.pack(...)
113
local strings = {}
114
for i=1, args.n do
115
local item = args[i]
116
local str = pptostring(item, customoptions)
117
if i == 1 then
118
capturedoutput = capturedoutput .. str
119
else
120
capturedoutput = capturedoutput .. "\t" .. str
121
end
122
end
123
end
124
)";
125
};
126
127
TEST_SUITE_BEGIN("ReplPrettyPrint");
128
129
TEST_CASE_FIXTURE(ReplFixture, "AdditionStatement")
130
{
131
runCode(L, "return 30 + 12");
132
CHECK(getCapturedOutput() == "42");
133
}
134
135
TEST_CASE_FIXTURE(ReplFixture, "TableLiteral")
136
{
137
runCode(L, "return {1, 2, 3, 4}");
138
CHECK(getCapturedOutput() == "{1, 2, 3, 4}");
139
}
140
141
TEST_CASE_FIXTURE(ReplFixture, "StringLiteral")
142
{
143
runCode(L, "return 'str'");
144
CHECK(getCapturedOutput() == "\"str\"");
145
}
146
147
TEST_CASE_FIXTURE(ReplFixture, "TableWithStringLiterals")
148
{
149
runCode(L, "return {1, 'two', 3, 'four'}");
150
CHECK(getCapturedOutput() == "{1, \"two\", 3, \"four\"}");
151
}
152
153
TEST_CASE_FIXTURE(ReplFixture, "MultipleArguments")
154
{
155
runCode(L, "return 3, 'three'");
156
CHECK(getCapturedOutput() == "3\t\"three\"");
157
}
158
159
TEST_SUITE_END();
160
161
TEST_SUITE_BEGIN("ReplCodeCompletion");
162
163
TEST_CASE_FIXTURE(ReplFixture, "CompleteGlobalVariables")
164
{
165
runCode(L, R"(
166
myvariable1 = 5
167
myvariable2 = 5
168
)");
169
{
170
// Try to complete globals that are added by the user's script
171
CompletionSet completions = getCompletionSet("myvar");
172
173
std::string prefix = "";
174
CHECK(completions.size() == 2);
175
CHECK(checkCompletion(completions, prefix, "myvariable1"));
176
CHECK(checkCompletion(completions, prefix, "myvariable2"));
177
}
178
179
{
180
// Try completing some builtin functions
181
CompletionSet completions = getCompletionSet("math.m");
182
183
std::string prefix = "math.";
184
CHECK(completions.size() == 4);
185
CHECK(checkCompletion(completions, prefix, "max("));
186
CHECK(checkCompletion(completions, prefix, "min("));
187
CHECK(checkCompletion(completions, prefix, "modf("));
188
CHECK(checkCompletion(completions, prefix, "map("));
189
}
190
}
191
192
TEST_CASE_FIXTURE(ReplFixture, "CompleteTableKeys")
193
{
194
runCode(L, R"(
195
t = { color = "red", size = 1, shape = "circle" }
196
)");
197
{
198
CompletionSet completions = getCompletionSet("t.");
199
200
std::string prefix = "t.";
201
CHECK(completions.size() == 3);
202
CHECK(checkCompletion(completions, prefix, "color"));
203
CHECK(checkCompletion(completions, prefix, "size"));
204
CHECK(checkCompletion(completions, prefix, "shape"));
205
}
206
207
{
208
CompletionSet completions = getCompletionSet("t.s");
209
210
std::string prefix = "t.";
211
CHECK(completions.size() == 2);
212
CHECK(checkCompletion(completions, prefix, "size"));
213
CHECK(checkCompletion(completions, prefix, "shape"));
214
}
215
}
216
217
TEST_CASE_FIXTURE(ReplFixture, "StringMethods")
218
{
219
runCode(L, R"(
220
s = ""
221
)");
222
{
223
CompletionSet completions = getCompletionSet("s:l");
224
225
std::string prefix = "s:";
226
CHECK(completions.size() == 2);
227
CHECK(checkCompletion(completions, prefix, "len("));
228
CHECK(checkCompletion(completions, prefix, "lower("));
229
}
230
}
231
232
TEST_CASE_FIXTURE(ReplFixture, "TableWithMetatableIndexTable")
233
{
234
runCode(L, R"(
235
-- Create 't' which is a table with a metatable with an __index table
236
mt = {}
237
mt.__index = mt
238
239
t = {}
240
setmetatable(t, mt)
241
242
mt.mtkey1 = {x="x value", y="y value", 1, 2}
243
mt.mtkey2 = 2
244
245
t.tkey1 = {data1 = 2, data2 = "str", 3, 4}
246
t.tkey2 = 4
247
)");
248
{
249
CompletionSet completions = getCompletionSet("t.t");
250
251
std::string prefix = "t.";
252
CHECK(completions.size() == 2);
253
CHECK(checkCompletion(completions, prefix, "tkey1"));
254
CHECK(checkCompletion(completions, prefix, "tkey2"));
255
}
256
{
257
CompletionSet completions = getCompletionSet("t.tkey1.data2:re");
258
259
std::string prefix = "t.tkey1.data2:";
260
CHECK(completions.size() == 2);
261
CHECK(checkCompletion(completions, prefix, "rep("));
262
CHECK(checkCompletion(completions, prefix, "reverse("));
263
}
264
{
265
CompletionSet completions = getCompletionSet("t.mtk");
266
267
std::string prefix = "t.";
268
CHECK(completions.size() == 2);
269
CHECK(checkCompletion(completions, prefix, "mtkey1"));
270
CHECK(checkCompletion(completions, prefix, "mtkey2"));
271
}
272
{
273
CompletionSet completions = getCompletionSet("t.mtkey1.");
274
275
std::string prefix = "t.mtkey1.";
276
CHECK(completions.size() == 2);
277
CHECK(checkCompletion(completions, prefix, "x"));
278
CHECK(checkCompletion(completions, prefix, "y"));
279
}
280
}
281
282
TEST_CASE_FIXTURE(ReplFixture, "TableWithMetatableIndexFunction")
283
{
284
runCode(L, R"(
285
-- Create 't' which is a table with a metatable with an __index function
286
mt = {}
287
mt.__index = function(table, key)
288
print("mt.__index called")
289
if key == "foo" then
290
return "FOO"
291
elseif key == "bar" then
292
return "BAR"
293
else
294
return nil
295
end
296
end
297
298
t = {}
299
setmetatable(t, mt)
300
t.tkey = 0
301
)");
302
{
303
CompletionSet completions = getCompletionSet("t.t");
304
305
std::string prefix = "t.";
306
CHECK(completions.size() == 1);
307
CHECK(checkCompletion(completions, prefix, "tkey"));
308
}
309
{
310
// t.foo is a valid key, but should not be completed because it requires calling an __index function
311
CompletionSet completions = getCompletionSet("t.foo");
312
313
CHECK(completions.size() == 0);
314
}
315
{
316
// t.foo is a valid key, but should not be found because it requires calling an __index function
317
CompletionSet completions = getCompletionSet("t.foo:");
318
319
CHECK(completions.size() == 0);
320
}
321
}
322
323
TEST_CASE_FIXTURE(ReplFixture, "TableWithMultipleMetatableIndexTables")
324
{
325
runCode(L, R"(
326
-- Create a table with a chain of metatables
327
mt2 = {}
328
mt2.__index = mt2
329
330
mt = {}
331
mt.__index = mt
332
setmetatable(mt, mt2)
333
334
t = {}
335
setmetatable(t, mt)
336
337
mt2.mt2key = {x=1, y=2}
338
mt.mtkey = 2
339
t.tkey = 3
340
)");
341
{
342
CompletionSet completions = getCompletionSet("t.");
343
344
std::string prefix = "t.";
345
CHECK(completions.size() == 4);
346
CHECK(checkCompletion(completions, prefix, "__index"));
347
CHECK(checkCompletion(completions, prefix, "tkey"));
348
CHECK(checkCompletion(completions, prefix, "mtkey"));
349
CHECK(checkCompletion(completions, prefix, "mt2key"));
350
}
351
{
352
CompletionSet completions = getCompletionSet("t.__index.");
353
354
std::string prefix = "t.__index.";
355
CHECK(completions.size() == 3);
356
CHECK(checkCompletion(completions, prefix, "__index"));
357
CHECK(checkCompletion(completions, prefix, "mtkey"));
358
CHECK(checkCompletion(completions, prefix, "mt2key"));
359
}
360
{
361
CompletionSet completions = getCompletionSet("t.mt2key.");
362
363
std::string prefix = "t.mt2key.";
364
CHECK(completions.size() == 2);
365
CHECK(checkCompletion(completions, prefix, "x"));
366
CHECK(checkCompletion(completions, prefix, "y"));
367
}
368
}
369
370
TEST_CASE_FIXTURE(ReplFixture, "TableWithDeepMetatableIndexTables")
371
{
372
runCode(L, R"(
373
-- Creates a table with a chain of metatables of length `count`
374
function makeChainedTable(count)
375
local result = {}
376
result.__index = result
377
result[string.format("entry%d", count)] = { count = count }
378
if count == 0 then
379
return result
380
else
381
return setmetatable(result, makeChainedTable(count - 1))
382
end
383
end
384
385
t30 = makeChainedTable(30)
386
t60 = makeChainedTable(60)
387
)");
388
{
389
// Check if entry0 exists
390
CompletionSet completions = getCompletionSet("t30.entry0");
391
392
std::string prefix = "t30.";
393
CHECK(checkCompletion(completions, prefix, "entry0"));
394
}
395
{
396
// Check if entry0.count exists
397
CompletionSet completions = getCompletionSet("t30.entry0.co");
398
399
std::string prefix = "t30.entry0.";
400
CHECK(checkCompletion(completions, prefix, "count"));
401
}
402
{
403
// Check if entry0 exists. With the max traversal limit of 50 in the repl, this should fail.
404
CompletionSet completions = getCompletionSet("t60.entry0");
405
406
CHECK(completions.size() == 0);
407
}
408
{
409
// Check if entry0.count exists. With the max traversal limit of 50 in the repl, this should fail.
410
CompletionSet completions = getCompletionSet("t60.entry0.co");
411
412
CHECK(completions.size() == 0);
413
}
414
}
415
416
TEST_SUITE_END();
417
418
TEST_SUITE_BEGIN("RegressionTests");
419
420
TEST_CASE_FIXTURE(ReplFixture, "InfiniteRecursion")
421
{
422
// If the infinite recrusion is not caught, test will fail
423
runCode(L, R"(
424
local NewProxyOne = newproxy(true)
425
local MetaTableOne = getmetatable(NewProxyOne)
426
MetaTableOne.__index = function()
427
return NewProxyOne.Game
428
end
429
print(NewProxyOne.HelloICauseACrash)
430
)");
431
}
432
433
TEST_CASE_FIXTURE(ReplFixture, "InteractiveStackReserve1")
434
{
435
// Reset stack reservation
436
lua_resume(L, nullptr, 0);
437
438
runCode(L, R"(
439
local t = {}
440
)");
441
}
442
443
TEST_CASE_FIXTURE(ReplFixture, "InteractiveStackReserve2")
444
{
445
// Reset stack reservation
446
lua_resume(L, nullptr, 0);
447
448
getCompletionSet("a");
449
}
450
451
TEST_SUITE_END();
452
453