Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/Fixture.h
2723 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#pragma once
3
4
#include "Luau/BuiltinTypeFunctions.h"
5
#include "Luau/Config.h"
6
#include "Luau/Error.h"
7
#include "Luau/FileResolver.h"
8
#include "Luau/Frontend.h"
9
#include "Luau/IostreamHelpers.h"
10
#include "Luau/Linter.h"
11
#include "Luau/Location.h"
12
#include "Luau/ModuleResolver.h"
13
#include "Luau/Scope.h"
14
#include "Luau/ToString.h"
15
#include "Luau/Type.h"
16
#include "Luau/TypeFunction.h"
17
18
#include "IostreamOptional.h"
19
#include "ScopedFlags.h"
20
21
#include "doctest.h"
22
#include <string>
23
#include <string_view>
24
#include <unordered_map>
25
#include <optional>
26
#include <vector>
27
28
LUAU_FASTFLAG(DebugLuauFreezeArena)
29
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
30
LUAU_FASTFLAG(DebugLuauForceAllOldSolverTests)
31
32
LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete);
33
LUAU_FASTFLAG(DebugLuauForceOldSolver)
34
35
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::DebugLuauForceOldSolver, !FFlag::DebugLuauForceAllNewSolverTests};
36
37
#define DOES_NOT_PASS_NEW_SOLVER_GUARD() DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(__LINE__)
38
39
#define DOES_NOT_PASS_OLD_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::DebugLuauForceOldSolver, FFlag::DebugLuauForceAllOldSolverTests};
40
41
#define DOES_NOT_PASS_OLD_SOLVER_GUARD() DOES_NOT_PASS_OLD_SOLVER_GUARD_IMPL(__LINE__)
42
43
44
45
namespace Luau
46
{
47
48
struct TypeChecker;
49
50
struct TestRequireNode : RequireNode
51
{
52
TestRequireNode(ModuleName moduleName, std::unordered_map<ModuleName, std::string>* allSources)
53
: moduleName(std::move(moduleName))
54
, allSources(allSources)
55
{
56
}
57
58
std::string getLabel() const override;
59
std::string getPathComponent() const override;
60
std::unique_ptr<RequireNode> resolvePathToNode(const std::string& path) const override;
61
std::vector<std::unique_ptr<RequireNode>> getChildren() const override;
62
std::vector<RequireAlias> getAvailableAliases() const override;
63
64
ModuleName moduleName;
65
std::unordered_map<ModuleName, std::string>* allSources;
66
};
67
68
struct TestFileResolver;
69
struct TestRequireSuggester : RequireSuggester
70
{
71
TestRequireSuggester(TestFileResolver* resolver)
72
: resolver(resolver)
73
{
74
}
75
76
std::unique_ptr<RequireNode> getNode(const ModuleName& name) const override;
77
TestFileResolver* resolver;
78
};
79
80
struct TestFileResolver
81
: FileResolver
82
, ModuleResolver
83
{
84
TestFileResolver()
85
: FileResolver(std::make_shared<TestRequireSuggester>(this))
86
{
87
}
88
89
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override;
90
91
const ModulePtr getModule(const ModuleName& moduleName) const override;
92
93
bool moduleExists(const ModuleName& moduleName) const override;
94
95
std::optional<SourceCode> readSource(const ModuleName& name) override;
96
97
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr, const TypeCheckLimits& limits) override;
98
99
std::string getHumanReadableModuleName(const ModuleName& name) const override;
100
101
std::optional<std::string> getEnvironmentForModule(const ModuleName& name) const override;
102
103
std::unordered_map<ModuleName, std::string> source;
104
std::unordered_map<ModuleName, SourceCode::Type> sourceTypes;
105
std::unordered_map<ModuleName, std::string> environments;
106
};
107
108
struct TestConfigResolver : ConfigResolver
109
{
110
Config defaultConfig;
111
std::unordered_map<ModuleName, Config> configFiles;
112
113
const Config& getConfig(const ModuleName& name, const TypeCheckLimits& limits = {}) const override;
114
};
115
116
struct Fixture
117
{
118
explicit Fixture(bool prepareAutocomplete = false);
119
120
explicit Fixture(const Fixture&) = delete;
121
Fixture& operator=(const Fixture&) = delete;
122
123
~Fixture();
124
125
// Throws Luau::ParseErrors if the parse fails.
126
AstStatBlock* parse(const std::string& source, const ParseOptions& parseOptions = {});
127
CheckResult check(Mode mode, const std::string& source, std::optional<FrontendOptions> = std::nullopt);
128
CheckResult check(const std::string& source, std::optional<FrontendOptions> = std::nullopt);
129
130
LintResult lint(const std::string& source, const std::optional<LintOptions>& lintOptions = {});
131
LintResult lintModule(const ModuleName& moduleName, const std::optional<LintOptions>& lintOptions = {});
132
133
/// Parse with all language extensions enabled
134
ParseResult parseEx(const std::string& source, const ParseOptions& parseOptions = {});
135
ParseResult tryParse(const std::string& source, const ParseOptions& parseOptions = {});
136
ParseResult matchParseError(const std::string& source, const std::string& message, std::optional<Location> location = std::nullopt);
137
// Verify a parse error occurs and the parse error message has the specified prefix
138
ParseResult matchParseErrorPrefix(const std::string& source, const std::string& prefix);
139
140
ModulePtr getMainModule(bool forAutocomplete = false);
141
SourceModule* getMainSourceModule();
142
143
std::optional<PrimitiveType::Type> getPrimitiveType(TypeId ty);
144
std::optional<TypeId> getType(const std::string& name, bool forAutocomplete = false);
145
TypeId requireType(const std::string& name);
146
TypeId requireType(const ModuleName& moduleName, const std::string& name);
147
TypeId requireType(const ModulePtr& module, const std::string& name);
148
TypeId requireType(const ScopePtr& scope, const std::string& name);
149
150
std::optional<TypeId> findTypeAtPosition(Position position);
151
TypeId requireTypeAtPosition(Position position);
152
std::optional<TypeId> findExpectedTypeAtPosition(Position position);
153
154
std::optional<TypeId> lookupType(const std::string& name);
155
std::optional<TypeId> lookupImportedType(const std::string& moduleAlias, const std::string& name);
156
TypeId requireTypeAlias(const std::string& name);
157
TypeId requireExportedType(const ModuleName& moduleName, const std::string& name);
158
159
TypeId parseType(std::string_view src);
160
161
// While most flags can be flipped inside the unit test, some code changes affect the state that is part of Fixture initialization
162
// Most often those are changes related to builtin type definitions.
163
// In that case, flag can be forced to 'true' using the example below:
164
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};
165
166
// Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it.
167
// This is useful for tracking down violations of Luau's memory model.
168
ScopedFastFlag sff_DebugLuauFreezeArena{FFlag::DebugLuauFreezeArena, true};
169
170
// This makes sure that errant cases of constraint solving failing to complete still pop up in tests.
171
ScopedFastFlag sff_DebugLuauAlwaysShowConstraintSolvingIncomplete{FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, true};
172
173
TestFileResolver fileResolver;
174
TestConfigResolver configResolver;
175
NullModuleResolver moduleResolver;
176
std::unique_ptr<SourceModule> sourceModule;
177
InternalErrorReporter ice;
178
Allocator allocator;
179
AstNameTable nameTable{allocator};
180
TypeArena arena;
181
182
std::string decorateWithTypes(const std::string& code);
183
184
void dumpErrors(std::ostream& os, const std::vector<TypeError>& errors);
185
186
void dumpErrors(const CheckResult& cr);
187
void dumpErrors(const ModulePtr& module);
188
void dumpErrors(const Module& module);
189
190
void validateErrors(const std::vector<TypeError>& errors);
191
192
std::string getErrors(const CheckResult& cr);
193
194
void registerTestTypes();
195
196
LoadDefinitionFileResult loadDefinition(const std::string& source, bool forAutocomplete = false);
197
// TODO: test theory about dynamic dispatch
198
NotNull<BuiltinTypes> getBuiltins();
199
const BuiltinTypeFunctions& getBuiltinTypeFunctions();
200
virtual Frontend& getFrontend();
201
202
// On platforms that support it, adjust our internal stack guard to
203
// limit how much address space we should use before we blow up. We
204
// use this to test the stack guard itself.
205
void limitStackSize(size_t size);
206
207
private:
208
bool hasDumpedErrors = false;
209
210
protected:
211
bool forAutocomplete = false;
212
std::optional<Frontend> frontend;
213
BuiltinTypes* builtinTypes = nullptr;
214
215
std::vector<ScopedFastInt> dynamicScopedInts;
216
};
217
218
struct BuiltinsFixture : Fixture
219
{
220
explicit BuiltinsFixture(bool prepareAutocomplete = false);
221
222
// For the purpose of our tests, we're always the latest version of type functions.
223
Frontend& getFrontend() override;
224
};
225
226
struct IsSubtypeFixture : Fixture
227
{
228
bool isSubtype(TypeId a, TypeId b);
229
};
230
231
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments);
232
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const AstExpr& pathExpr);
233
234
ModuleName fromString(std::string_view name);
235
236
template<typename T>
237
std::optional<T> get(const std::map<Name, T>& map, const Name& name)
238
{
239
auto it = map.find(name);
240
if (it != map.end())
241
return std::optional<T>(it->second);
242
else
243
return std::nullopt;
244
}
245
246
std::string rep(const std::string& s, size_t n);
247
248
bool isInArena(TypeId t, const TypeArena& arena);
249
250
void dumpErrors(const ModulePtr& module);
251
void dumpErrors(const Module& module);
252
void dump(const std::string& name, TypeId ty);
253
void dump(const std::vector<Constraint>& constraints);
254
255
std::optional<TypeId> lookupName(ScopePtr scope, const std::string& name); // Warning: This function runs in O(n**2)
256
257
std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);
258
259
void registerHiddenTypes(Frontend& frontend);
260
void createSomeExternTypes(Frontend& frontend);
261
262
template<typename E>
263
const E* findError(const CheckResult& result)
264
{
265
for (const auto& e : result.errors)
266
{
267
if (auto p = get<E>(e))
268
return p;
269
}
270
271
return nullptr;
272
}
273
274
} // namespace Luau
275
276
#define LUAU_REQUIRE_ERRORS(result) \
277
do \
278
{ \
279
auto&& r = (result); \
280
validateErrors(r.errors); \
281
REQUIRE(!r.errors.empty()); \
282
} while (false)
283
284
#define LUAU_REQUIRE_ERROR_COUNT(count, result) \
285
do \
286
{ \
287
auto&& r = (result); \
288
validateErrors(r.errors); \
289
REQUIRE_MESSAGE(count == r.errors.size(), getErrors(r)); \
290
} while (false)
291
292
#define LUAU_REQUIRE_NO_ERRORS(result) LUAU_REQUIRE_ERROR_COUNT(0, result)
293
294
#define LUAU_CHECK_ERRORS(result) \
295
do \
296
{ \
297
auto&& r = (result); \
298
validateErrors(r.errors); \
299
CHECK(!r.errors.empty()); \
300
} while (false)
301
302
#define LUAU_CHECK_ERROR_COUNT(count, result) \
303
do \
304
{ \
305
auto&& r = (result); \
306
validateErrors(r.errors); \
307
CHECK_MESSAGE(count == r.errors.size(), getErrors(r)); \
308
} while (false)
309
310
#define LUAU_CHECK_NO_ERRORS(result) LUAU_CHECK_ERROR_COUNT(0, result)
311
312
#define LUAU_CHECK_HAS_KEY(map, key) \
313
do \
314
{ \
315
auto&& _m = (map); \
316
auto&& _k = (key); \
317
const size_t count = _m.count(_k); \
318
CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \
319
if (!count) \
320
{ \
321
MESSAGE("Keys: (count " << _m.size() << ")"); \
322
for (const auto& [k, v] : _m) \
323
{ \
324
MESSAGE("\tkey: " << k); \
325
} \
326
} \
327
} while (false)
328
329
#define LUAU_CHECK_HAS_NO_KEY(map, key) \
330
do \
331
{ \
332
auto&& _m = (map); \
333
auto&& _k = (key); \
334
const size_t count = _m.count(_k); \
335
CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \
336
if (count) \
337
{ \
338
MESSAGE("Keys: (count " << _m.size() << ")"); \
339
for (const auto& [k, v] : _m) \
340
{ \
341
MESSAGE("\tkey: " << k); \
342
} \
343
} \
344
} while (false)
345
346
#define LUAU_REQUIRE_ERROR(result, Type) \
347
do \
348
{ \
349
using T = Type; \
350
const auto& res = (result); \
351
if (!findError<T>(res)) \
352
{ \
353
dumpErrors(res); \
354
REQUIRE_MESSAGE(false, "Expected to find " #Type " error"); \
355
} \
356
} while (false)
357
358
#define LUAU_CHECK_ERROR(result, Type) \
359
do \
360
{ \
361
using T = Type; \
362
const auto& res = (result); \
363
if (!findError<T>(res)) \
364
{ \
365
dumpErrors(res); \
366
CHECK_MESSAGE(false, "Expected to find " #Type " error"); \
367
} \
368
} while (false)
369
370
#define LUAU_REQUIRE_NO_ERROR(result, Type) \
371
do \
372
{ \
373
using T = Type; \
374
const auto& res = (result); \
375
if (findError<T>(res)) \
376
{ \
377
dumpErrors(res); \
378
REQUIRE_MESSAGE(false, "Expected to find no " #Type " error"); \
379
} \
380
} while (false)
381
382
#define LUAU_CHECK_NO_ERROR(result, Type) \
383
do \
384
{ \
385
using T = Type; \
386
const auto& res = (result); \
387
if (findError<T>(res)) \
388
{ \
389
dumpErrors(res); \
390
CHECK_MESSAGE(false, "Expected to find no " #Type " error"); \
391
} \
392
} while (false)
393
394
#define CHECK_LONG_STRINGS_EQ(a, b) \
395
do \
396
{ \
397
const auto aa = (a); \
398
const auto bb = (b); \
399
const auto aLines = split(aa, '\n'); \
400
const auto bLines = split(bb, '\n'); \
401
CHECK_MESSAGE(aLines.size() == bLines.size(), "Line counts don't match: " << aLines.size() << " != " << bLines.size()); \
402
bool anyWrong = false; \
403
for (size_t i = 0; i < std::min(aLines.size(), bLines.size()); ++i) \
404
{ \
405
auto aLine = strip(aLines.at(i)); \
406
auto bLine = strip(bLines.at(i)); \
407
if (aLine != bLine) \
408
anyWrong = true; \
409
CHECK_MESSAGE(aLine == bLine, "Mismatch on line " << i << " between:\n\t«" << aLine << "»\nand\t«" << bLine << "»\n"); \
410
} \
411
if (anyWrong) \
412
{ \
413
MESSAGE(aa); \
414
MESSAGE(bb); \
415
} \
416
} while (0)
417
418