roblox-opencode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/commands/setup-game.md +108 -0
- package/commands/sync-check.md +53 -0
- package/core/roblox-core.md +93 -0
- package/dist/server.js +167 -0
- package/package.json +35 -0
- package/skills/roblox-analytics/SKILL.md +277 -0
- package/skills/roblox-analytics/references/event-batcher.luau +75 -0
- package/skills/roblox-animation-vfx/SKILL.md +1325 -0
- package/skills/roblox-architecture/SKILL.md +863 -0
- package/skills/roblox-architecture/references/combat-systems.md +1381 -0
- package/skills/roblox-code-review/SKILL.md +687 -0
- package/skills/roblox-data/SKILL.md +889 -0
- package/skills/roblox-data/references/inventory-systems.md +1729 -0
- package/skills/roblox-debug/SKILL.md +99 -0
- package/skills/roblox-gui/SKILL.md +1103 -0
- package/skills/roblox-gui-fusion/SKILL.md +150 -0
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
- package/skills/roblox-gui-fusion/references/shop.luau +411 -0
- package/skills/roblox-luau-mastery/SKILL.md +1519 -0
- package/skills/roblox-monetization/SKILL.md +1084 -0
- package/skills/roblox-monetization/references/process-receipt.luau +131 -0
- package/skills/roblox-networking/SKILL.md +669 -0
- package/skills/roblox-networking/references/remote-validator.luau +193 -0
- package/skills/roblox-publish-checklist/SKILL.md +128 -0
- package/skills/roblox-runtime/SKILL.md +753 -0
- package/skills/roblox-sharp-edges/SKILL.md +295 -0
- package/skills/roblox-sync/SKILL.md +126 -0
- package/skills/roblox-testing/SKILL.md +943 -0
- package/skills/roblox-tooling/SKILL.md +150 -0
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
- package/vendor/LICENSES/promise-LICENSE +21 -0
- package/vendor/LICENSES/t-LICENSE +21 -0
- package/vendor/LICENSES/testez-LICENSE +201 -0
- package/vendor/README.md +84 -0
- package/vendor/fusion/Animation/ExternalTime.luau +84 -0
- package/vendor/fusion/Animation/Spring.luau +322 -0
- package/vendor/fusion/Animation/Stopwatch.luau +128 -0
- package/vendor/fusion/Animation/Tween.luau +187 -0
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
- package/vendor/fusion/Animation/lerpType.luau +164 -0
- package/vendor/fusion/Animation/packType.luau +100 -0
- package/vendor/fusion/Animation/springCoefficients.luau +80 -0
- package/vendor/fusion/Animation/unpackType.luau +103 -0
- package/vendor/fusion/Colour/Oklab.luau +70 -0
- package/vendor/fusion/Colour/sRGB.luau +55 -0
- package/vendor/fusion/External.luau +168 -0
- package/vendor/fusion/ExternalDebug.luau +70 -0
- package/vendor/fusion/Graph/Observer.luau +114 -0
- package/vendor/fusion/Graph/castToGraph.luau +29 -0
- package/vendor/fusion/Graph/change.luau +81 -0
- package/vendor/fusion/Graph/depend.luau +33 -0
- package/vendor/fusion/Graph/evaluate.luau +56 -0
- package/vendor/fusion/Instances/Attribute.luau +58 -0
- package/vendor/fusion/Instances/AttributeChange.luau +47 -0
- package/vendor/fusion/Instances/AttributeOut.luau +63 -0
- package/vendor/fusion/Instances/Child.luau +21 -0
- package/vendor/fusion/Instances/Children.luau +148 -0
- package/vendor/fusion/Instances/Hydrate.luau +33 -0
- package/vendor/fusion/Instances/New.luau +53 -0
- package/vendor/fusion/Instances/OnChange.luau +50 -0
- package/vendor/fusion/Instances/OnEvent.luau +54 -0
- package/vendor/fusion/Instances/Out.luau +69 -0
- package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
- package/vendor/fusion/Instances/defaultProps.luau +194 -0
- package/vendor/fusion/LICENSE +21 -0
- package/vendor/fusion/Logging/formatError.luau +49 -0
- package/vendor/fusion/Logging/messages.luau +52 -0
- package/vendor/fusion/Logging/parseError.luau +25 -0
- package/vendor/fusion/Memory/checkLifetime.luau +134 -0
- package/vendor/fusion/Memory/deriveScope.luau +24 -0
- package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
- package/vendor/fusion/Memory/doCleanup.luau +79 -0
- package/vendor/fusion/Memory/innerScope.luau +34 -0
- package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
- package/vendor/fusion/Memory/needsDestruction.luau +17 -0
- package/vendor/fusion/Memory/poisonScope.luau +34 -0
- package/vendor/fusion/Memory/scopePool.luau +55 -0
- package/vendor/fusion/Memory/scoped.luau +27 -0
- package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
- package/vendor/fusion/RobloxExternal.luau +98 -0
- package/vendor/fusion/State/Computed.luau +139 -0
- package/vendor/fusion/State/For/Disassembly.luau +211 -0
- package/vendor/fusion/State/For/ForTypes.luau +30 -0
- package/vendor/fusion/State/For/init.luau +110 -0
- package/vendor/fusion/State/ForKeys.luau +94 -0
- package/vendor/fusion/State/ForPairs.luau +97 -0
- package/vendor/fusion/State/ForValues.luau +94 -0
- package/vendor/fusion/State/Value.luau +88 -0
- package/vendor/fusion/State/castToState.luau +26 -0
- package/vendor/fusion/State/peek.luau +31 -0
- package/vendor/fusion/State/updateAll.luau +1 -0
- package/vendor/fusion/Types.luau +314 -0
- package/vendor/fusion/Utility/Contextual.luau +91 -0
- package/vendor/fusion/Utility/Safe.luau +23 -0
- package/vendor/fusion/Utility/isSimilar.luau +29 -0
- package/vendor/fusion/Utility/merge.luau +35 -0
- package/vendor/fusion/Utility/nameOf.luau +35 -0
- package/vendor/fusion/Utility/never.luau +14 -0
- package/vendor/fusion/Utility/nicknames.luau +11 -0
- package/vendor/fusion/Utility/xtypeof.luau +27 -0
- package/vendor/fusion/init.luau +82 -0
- package/vendor/profilestore/init.luau +2243 -0
- package/vendor/promise/init.luau +1982 -0
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
- package/vendor/rbxutil/buffer-util/Types.luau +60 -0
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
- package/vendor/rbxutil/buffer-util/init.luau +41 -0
- package/vendor/rbxutil/buffer-util/package.json +16 -0
- package/vendor/rbxutil/buffer-util/wally.toml +9 -0
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
- package/vendor/rbxutil/comm/Client/init.luau +135 -0
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
- package/vendor/rbxutil/comm/Server/init.luau +140 -0
- package/vendor/rbxutil/comm/Types.luau +18 -0
- package/vendor/rbxutil/comm/Util.luau +27 -0
- package/vendor/rbxutil/comm/init.luau +35 -0
- package/vendor/rbxutil/comm/wally.toml +13 -0
- package/vendor/rbxutil/component/init.luau +759 -0
- package/vendor/rbxutil/component/init.test.luau +311 -0
- package/vendor/rbxutil/component/wally.toml +14 -0
- package/vendor/rbxutil/concur/init.luau +542 -0
- package/vendor/rbxutil/concur/init.test.luau +364 -0
- package/vendor/rbxutil/concur/wally.toml +8 -0
- package/vendor/rbxutil/enum-list/init.luau +101 -0
- package/vendor/rbxutil/enum-list/init.test.luau +91 -0
- package/vendor/rbxutil/enum-list/wally.toml +8 -0
- package/vendor/rbxutil/find/index.d.ts +20 -0
- package/vendor/rbxutil/find/init.luau +44 -0
- package/vendor/rbxutil/find/package.json +17 -0
- package/vendor/rbxutil/find/wally.toml +8 -0
- package/vendor/rbxutil/input/Gamepad.luau +559 -0
- package/vendor/rbxutil/input/Keyboard.luau +124 -0
- package/vendor/rbxutil/input/Mouse.luau +278 -0
- package/vendor/rbxutil/input/PreferredInput.luau +91 -0
- package/vendor/rbxutil/input/Touch.luau +120 -0
- package/vendor/rbxutil/input/init.luau +33 -0
- package/vendor/rbxutil/input/wally.toml +12 -0
- package/vendor/rbxutil/loader/index.d.ts +15 -0
- package/vendor/rbxutil/loader/init.luau +137 -0
- package/vendor/rbxutil/loader/wally.toml +8 -0
- package/vendor/rbxutil/log/index.d.ts +38 -0
- package/vendor/rbxutil/log/init.luau +746 -0
- package/vendor/rbxutil/log/wally.toml +8 -0
- package/vendor/rbxutil/net/init.luau +190 -0
- package/vendor/rbxutil/net/wally.toml +8 -0
- package/vendor/rbxutil/option/index.d.ts +44 -0
- package/vendor/rbxutil/option/init.luau +489 -0
- package/vendor/rbxutil/option/init.test.luau +342 -0
- package/vendor/rbxutil/option/wally.toml +8 -0
- package/vendor/rbxutil/pid/index.d.ts +53 -0
- package/vendor/rbxutil/pid/init.luau +195 -0
- package/vendor/rbxutil/pid/package.json +16 -0
- package/vendor/rbxutil/pid/wally.toml +9 -0
- package/vendor/rbxutil/quaternion/index.d.ts +117 -0
- package/vendor/rbxutil/quaternion/init.luau +570 -0
- package/vendor/rbxutil/quaternion/package.json +16 -0
- package/vendor/rbxutil/quaternion/wally.toml +9 -0
- package/vendor/rbxutil/query/index.d.ts +43 -0
- package/vendor/rbxutil/query/init.luau +117 -0
- package/vendor/rbxutil/query/package.json +18 -0
- package/vendor/rbxutil/query/wally.toml +9 -0
- package/vendor/rbxutil/sequent/index.d.ts +28 -0
- package/vendor/rbxutil/sequent/init.luau +340 -0
- package/vendor/rbxutil/sequent/package.json +16 -0
- package/vendor/rbxutil/sequent/wally.toml +9 -0
- package/vendor/rbxutil/ser/init.luau +175 -0
- package/vendor/rbxutil/ser/init.test.luau +50 -0
- package/vendor/rbxutil/ser/wally.toml +11 -0
- package/vendor/rbxutil/shake/index.d.ts +36 -0
- package/vendor/rbxutil/shake/init.luau +532 -0
- package/vendor/rbxutil/shake/init.test.luau +267 -0
- package/vendor/rbxutil/shake/package.json +16 -0
- package/vendor/rbxutil/shake/wally.toml +9 -0
- package/vendor/rbxutil/signal/index.d.ts +100 -0
- package/vendor/rbxutil/signal/init.luau +432 -0
- package/vendor/rbxutil/signal/init.test.luau +190 -0
- package/vendor/rbxutil/signal/package.json +17 -0
- package/vendor/rbxutil/signal/wally.toml +9 -0
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
- package/vendor/rbxutil/silo/Util.luau +55 -0
- package/vendor/rbxutil/silo/init.luau +338 -0
- package/vendor/rbxutil/silo/init.test.luau +215 -0
- package/vendor/rbxutil/silo/wally.toml +8 -0
- package/vendor/rbxutil/spring/index.d.ts +40 -0
- package/vendor/rbxutil/spring/init.luau +97 -0
- package/vendor/rbxutil/spring/package.json +17 -0
- package/vendor/rbxutil/spring/wally.toml +8 -0
- package/vendor/rbxutil/stream/index.d.ts +88 -0
- package/vendor/rbxutil/stream/init.luau +597 -0
- package/vendor/rbxutil/stream/package.json +18 -0
- package/vendor/rbxutil/stream/wally.toml +9 -0
- package/vendor/rbxutil/streamable/Streamable.luau +202 -0
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
- package/vendor/rbxutil/streamable/init.luau +8 -0
- package/vendor/rbxutil/streamable/wally.toml +12 -0
- package/vendor/rbxutil/symbol/init.luau +56 -0
- package/vendor/rbxutil/symbol/init.test.luau +37 -0
- package/vendor/rbxutil/symbol/wally.toml +8 -0
- package/vendor/rbxutil/table-util/init.luau +938 -0
- package/vendor/rbxutil/table-util/init.test.luau +439 -0
- package/vendor/rbxutil/table-util/wally.toml +8 -0
- package/vendor/rbxutil/task-queue/index.d.ts +27 -0
- package/vendor/rbxutil/task-queue/init.luau +97 -0
- package/vendor/rbxutil/task-queue/wally.toml +8 -0
- package/vendor/rbxutil/timer/index.d.ts +81 -0
- package/vendor/rbxutil/timer/init.luau +249 -0
- package/vendor/rbxutil/timer/init.test.luau +73 -0
- package/vendor/rbxutil/timer/wally.toml +11 -0
- package/vendor/rbxutil/tree/index.d.ts +15 -0
- package/vendor/rbxutil/tree/init.luau +137 -0
- package/vendor/rbxutil/tree/wally.toml +8 -0
- package/vendor/rbxutil/trove/index.d.ts +46 -0
- package/vendor/rbxutil/trove/init.luau +787 -0
- package/vendor/rbxutil/trove/init.test.luau +203 -0
- package/vendor/rbxutil/trove/wally.toml +8 -0
- package/vendor/rbxutil/typed-remote/init.luau +196 -0
- package/vendor/rbxutil/typed-remote/wally.toml +8 -0
- package/vendor/rbxutil/wait-for/index.d.ts +17 -0
- package/vendor/rbxutil/wait-for/init.luau +257 -0
- package/vendor/rbxutil/wait-for/init.test.luau +182 -0
- package/vendor/rbxutil/wait-for/wally.toml +11 -0
- package/vendor/t/t.lua +1350 -0
- package/vendor/testez/Context.lua +26 -0
- package/vendor/testez/Expectation.lua +311 -0
- package/vendor/testez/ExpectationContext.lua +38 -0
- package/vendor/testez/LifecycleHooks.lua +89 -0
- package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
- package/vendor/testez/Reporters/TextReporter.lua +106 -0
- package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
- package/vendor/testez/TestBootstrap.lua +147 -0
- package/vendor/testez/TestEnum.lua +28 -0
- package/vendor/testez/TestPlan.lua +304 -0
- package/vendor/testez/TestPlanner.lua +40 -0
- package/vendor/testez/TestResults.lua +112 -0
- package/vendor/testez/TestRunner.lua +188 -0
- package/vendor/testez/TestSession.lua +243 -0
- package/vendor/testez/init.lua +40 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Copy of TextReporter that doesn't output successful tests.
|
|
3
|
+
|
|
4
|
+
This should be temporary, it's just a workaround to make CI environments
|
|
5
|
+
happy in the short-term.
|
|
6
|
+
]]
|
|
7
|
+
|
|
8
|
+
local TestService = game:GetService("TestService")
|
|
9
|
+
|
|
10
|
+
local TestEnum = require(script.Parent.Parent.TestEnum)
|
|
11
|
+
|
|
12
|
+
local INDENT = (" "):rep(3)
|
|
13
|
+
local STATUS_SYMBOLS = {
|
|
14
|
+
[TestEnum.TestStatus.Success] = "+",
|
|
15
|
+
[TestEnum.TestStatus.Failure] = "-",
|
|
16
|
+
[TestEnum.TestStatus.Skipped] = "~"
|
|
17
|
+
}
|
|
18
|
+
local UNKNOWN_STATUS_SYMBOL = "?"
|
|
19
|
+
|
|
20
|
+
local TextReporterQuiet = {}
|
|
21
|
+
|
|
22
|
+
local function reportNode(node, buffer, level)
|
|
23
|
+
buffer = buffer or {}
|
|
24
|
+
level = level or 0
|
|
25
|
+
|
|
26
|
+
if node.status == TestEnum.TestStatus.Skipped then
|
|
27
|
+
return buffer
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
local line
|
|
31
|
+
|
|
32
|
+
if node.status ~= TestEnum.TestStatus.Success then
|
|
33
|
+
local symbol = STATUS_SYMBOLS[node.status] or UNKNOWN_STATUS_SYMBOL
|
|
34
|
+
|
|
35
|
+
line = ("%s[%s] %s"):format(
|
|
36
|
+
INDENT:rep(level),
|
|
37
|
+
symbol,
|
|
38
|
+
node.planNode.phrase
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
table.insert(buffer, line)
|
|
43
|
+
|
|
44
|
+
for _, child in ipairs(node.children) do
|
|
45
|
+
reportNode(child, buffer, level + 1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return buffer
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
local function reportRoot(node)
|
|
52
|
+
local buffer = {}
|
|
53
|
+
|
|
54
|
+
for _, child in ipairs(node.children) do
|
|
55
|
+
reportNode(child, buffer, 0)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return buffer
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
local function report(root)
|
|
62
|
+
local buffer = reportRoot(root)
|
|
63
|
+
|
|
64
|
+
return table.concat(buffer, "\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
function TextReporterQuiet.report(results)
|
|
68
|
+
local resultBuffer = {
|
|
69
|
+
"Test results:",
|
|
70
|
+
report(results),
|
|
71
|
+
("%d passed, %d failed, %d skipped"):format(
|
|
72
|
+
results.successCount,
|
|
73
|
+
results.failureCount,
|
|
74
|
+
results.skippedCount
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
print(table.concat(resultBuffer, "\n"))
|
|
79
|
+
|
|
80
|
+
if results.failureCount > 0 then
|
|
81
|
+
print(("%d test nodes reported failures."):format(results.failureCount))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if #results.errors > 0 then
|
|
85
|
+
print("Errors reported by tests:")
|
|
86
|
+
print("")
|
|
87
|
+
|
|
88
|
+
for _, message in ipairs(results.errors) do
|
|
89
|
+
TestService:Error(message)
|
|
90
|
+
|
|
91
|
+
-- Insert a blank line after each error
|
|
92
|
+
print("")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
return TextReporterQuiet
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Provides an interface to quickly run and report tests from a given object.
|
|
3
|
+
]]
|
|
4
|
+
|
|
5
|
+
local TestPlanner = require(script.Parent.TestPlanner)
|
|
6
|
+
local TestRunner = require(script.Parent.TestRunner)
|
|
7
|
+
local TextReporter = require(script.Parent.Reporters.TextReporter)
|
|
8
|
+
|
|
9
|
+
local TestBootstrap = {}
|
|
10
|
+
|
|
11
|
+
local function stripSpecSuffix(name)
|
|
12
|
+
return (name:gsub("%.spec$", ""))
|
|
13
|
+
end
|
|
14
|
+
local function isSpecScript(aScript)
|
|
15
|
+
return aScript:IsA("ModuleScript") and aScript.Name:match("%.spec$")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
local function getPath(module, root)
|
|
19
|
+
root = root or game
|
|
20
|
+
|
|
21
|
+
local path = {}
|
|
22
|
+
local last = module
|
|
23
|
+
|
|
24
|
+
if last.Name == "init.spec" then
|
|
25
|
+
-- Use the directory's node for init.spec files.
|
|
26
|
+
last = last.Parent
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
while last ~= nil and last ~= root do
|
|
30
|
+
table.insert(path, stripSpecSuffix(last.Name))
|
|
31
|
+
last = last.Parent
|
|
32
|
+
end
|
|
33
|
+
table.insert(path, stripSpecSuffix(root.Name))
|
|
34
|
+
|
|
35
|
+
return path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
local function toStringPath(tablePath)
|
|
39
|
+
local stringPath = ""
|
|
40
|
+
local first = true
|
|
41
|
+
for _, element in ipairs(tablePath) do
|
|
42
|
+
if first then
|
|
43
|
+
stringPath = element
|
|
44
|
+
first = false
|
|
45
|
+
else
|
|
46
|
+
stringPath = element .. " " .. stringPath
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
return stringPath
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
function TestBootstrap:getModulesImpl(root, modules, current)
|
|
53
|
+
modules = modules or {}
|
|
54
|
+
current = current or root
|
|
55
|
+
|
|
56
|
+
if isSpecScript(current) then
|
|
57
|
+
local method = require(current)
|
|
58
|
+
local path = getPath(current, root)
|
|
59
|
+
local pathString = toStringPath(path)
|
|
60
|
+
|
|
61
|
+
table.insert(modules, {
|
|
62
|
+
method = method,
|
|
63
|
+
path = path,
|
|
64
|
+
pathStringForSorting = pathString:lower()
|
|
65
|
+
})
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
--[[
|
|
70
|
+
Find all the ModuleScripts in this tree that are tests.
|
|
71
|
+
]]
|
|
72
|
+
function TestBootstrap:getModules(root)
|
|
73
|
+
local modules = {}
|
|
74
|
+
|
|
75
|
+
self:getModulesImpl(root, modules)
|
|
76
|
+
|
|
77
|
+
for _, child in ipairs(root:GetDescendants()) do
|
|
78
|
+
self:getModulesImpl(root, modules, child)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return modules
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
--[[
|
|
85
|
+
Runs all test and reports the results using the given test reporter.
|
|
86
|
+
|
|
87
|
+
If no reporter is specified, a reasonable default is provided.
|
|
88
|
+
|
|
89
|
+
This function demonstrates the expected workflow with this testing system:
|
|
90
|
+
1. Locate test modules
|
|
91
|
+
2. Generate test plan
|
|
92
|
+
3. Run test plan
|
|
93
|
+
4. Report test results
|
|
94
|
+
|
|
95
|
+
This means we could hypothetically present a GUI to the developer that shows
|
|
96
|
+
the test plan before we execute it, allowing them to toggle specific tests
|
|
97
|
+
before they're run, but after they've been identified!
|
|
98
|
+
]]
|
|
99
|
+
function TestBootstrap:run(roots, reporter, otherOptions)
|
|
100
|
+
reporter = reporter or TextReporter
|
|
101
|
+
|
|
102
|
+
otherOptions = otherOptions or {}
|
|
103
|
+
local showTimingInfo = otherOptions["showTimingInfo"] or false
|
|
104
|
+
local testNamePattern = otherOptions["testNamePattern"]
|
|
105
|
+
local extraEnvironment = otherOptions["extraEnvironment"] or {}
|
|
106
|
+
|
|
107
|
+
if type(roots) ~= "table" then
|
|
108
|
+
error(("Bad argument #1 to TestBootstrap:run. Expected table, got %s"):format(typeof(roots)), 2)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
local startTime = tick()
|
|
112
|
+
|
|
113
|
+
local modules = {}
|
|
114
|
+
for _, subRoot in ipairs(roots) do
|
|
115
|
+
local newModules = self:getModules(subRoot)
|
|
116
|
+
|
|
117
|
+
for _, newModule in ipairs(newModules) do
|
|
118
|
+
table.insert(modules, newModule)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
local afterModules = tick()
|
|
123
|
+
|
|
124
|
+
local plan = TestPlanner.createPlan(modules, testNamePattern, extraEnvironment)
|
|
125
|
+
local afterPlan = tick()
|
|
126
|
+
|
|
127
|
+
local results = TestRunner.runPlan(plan)
|
|
128
|
+
local afterRun = tick()
|
|
129
|
+
|
|
130
|
+
reporter.report(results)
|
|
131
|
+
local afterReport = tick()
|
|
132
|
+
|
|
133
|
+
if showTimingInfo then
|
|
134
|
+
local timing = {
|
|
135
|
+
("Took %f seconds to locate test modules"):format(afterModules - startTime),
|
|
136
|
+
("Took %f seconds to create test plan"):format(afterPlan - afterModules),
|
|
137
|
+
("Took %f seconds to run tests"):format(afterRun - afterPlan),
|
|
138
|
+
("Took %f seconds to report tests"):format(afterReport - afterRun),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
print(table.concat(timing, "\n"))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
return results
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
return TestBootstrap
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Constants used throughout the testing framework.
|
|
3
|
+
]]
|
|
4
|
+
|
|
5
|
+
local TestEnum = {}
|
|
6
|
+
|
|
7
|
+
TestEnum.TestStatus = {
|
|
8
|
+
Success = "Success",
|
|
9
|
+
Failure = "Failure",
|
|
10
|
+
Skipped = "Skipped"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
TestEnum.NodeType = {
|
|
14
|
+
Describe = "Describe",
|
|
15
|
+
It = "It",
|
|
16
|
+
BeforeAll = "BeforeAll",
|
|
17
|
+
AfterAll = "AfterAll",
|
|
18
|
+
BeforeEach = "BeforeEach",
|
|
19
|
+
AfterEach = "AfterEach"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
TestEnum.NodeModifier = {
|
|
23
|
+
None = "None",
|
|
24
|
+
Skip = "Skip",
|
|
25
|
+
Focus = "Focus"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return TestEnum
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Represents a tree of tests that have been loaded but not necessarily
|
|
3
|
+
executed yet.
|
|
4
|
+
|
|
5
|
+
TestPlan objects are produced by TestPlanner.
|
|
6
|
+
]]
|
|
7
|
+
|
|
8
|
+
local TestEnum = require(script.Parent.TestEnum)
|
|
9
|
+
local Expectation = require(script.Parent.Expectation)
|
|
10
|
+
|
|
11
|
+
local function newEnvironment(currentNode, extraEnvironment)
|
|
12
|
+
local env = {}
|
|
13
|
+
|
|
14
|
+
if extraEnvironment then
|
|
15
|
+
if type(extraEnvironment) ~= "table" then
|
|
16
|
+
error(("Bad argument #2 to newEnvironment. Expected table, got %s"):format(
|
|
17
|
+
typeof(extraEnvironment)), 2)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
for key, value in pairs(extraEnvironment) do
|
|
21
|
+
env[key] = value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
local function addChild(phrase, callback, nodeType, nodeModifier)
|
|
26
|
+
local node = currentNode:addChild(phrase, nodeType, nodeModifier)
|
|
27
|
+
node.callback = callback
|
|
28
|
+
if nodeType == TestEnum.NodeType.Describe then
|
|
29
|
+
node:expand()
|
|
30
|
+
end
|
|
31
|
+
return node
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
function env.describeFOCUS(phrase, callback)
|
|
35
|
+
addChild(phrase, callback, TestEnum.NodeType.Describe, TestEnum.NodeModifier.Focus)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
function env.describeSKIP(phrase, callback)
|
|
39
|
+
addChild(phrase, callback, TestEnum.NodeType.Describe, TestEnum.NodeModifier.Skip)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
function env.describe(phrase, callback, nodeModifier)
|
|
43
|
+
addChild(phrase, callback, TestEnum.NodeType.Describe, TestEnum.NodeModifier.None)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
function env.itFOCUS(phrase, callback)
|
|
47
|
+
addChild(phrase, callback, TestEnum.NodeType.It, TestEnum.NodeModifier.Focus)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
function env.itSKIP(phrase, callback)
|
|
51
|
+
addChild(phrase, callback, TestEnum.NodeType.It, TestEnum.NodeModifier.Skip)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
function env.itFIXME(phrase, callback)
|
|
55
|
+
local node = addChild(phrase, callback, TestEnum.NodeType.It, TestEnum.NodeModifier.Skip)
|
|
56
|
+
warn("FIXME: broken test", node:getFullName())
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
function env.it(phrase, callback, nodeModifier)
|
|
60
|
+
addChild(phrase, callback, TestEnum.NodeType.It, TestEnum.NodeModifier.None)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
-- Incrementing counter used to ensure that beforeAll, afterAll, beforeEach, afterEach have unique phrases
|
|
64
|
+
local lifecyclePhaseId = 0
|
|
65
|
+
|
|
66
|
+
local lifecycleHooks = {
|
|
67
|
+
[TestEnum.NodeType.BeforeAll] = "beforeAll",
|
|
68
|
+
[TestEnum.NodeType.AfterAll] = "afterAll",
|
|
69
|
+
[TestEnum.NodeType.BeforeEach] = "beforeEach",
|
|
70
|
+
[TestEnum.NodeType.AfterEach] = "afterEach"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for nodeType, name in pairs(lifecycleHooks) do
|
|
74
|
+
env[name] = function(callback)
|
|
75
|
+
addChild(name .. "_" .. tostring(lifecyclePhaseId), callback, nodeType, TestEnum.NodeModifier.None)
|
|
76
|
+
lifecyclePhaseId = lifecyclePhaseId + 1
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
function env.FIXME(optionalMessage)
|
|
81
|
+
warn("FIXME: broken test", currentNode:getFullName(), optionalMessage or "")
|
|
82
|
+
|
|
83
|
+
currentNode.modifier = TestEnum.NodeModifier.Skip
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
function env.FOCUS()
|
|
87
|
+
currentNode.modifier = TestEnum.NodeModifier.Focus
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
function env.SKIP()
|
|
91
|
+
currentNode.modifier = TestEnum.NodeModifier.Skip
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
--[[
|
|
95
|
+
This function is deprecated. Calling it is a no-op beyond generating a
|
|
96
|
+
warning.
|
|
97
|
+
]]
|
|
98
|
+
function env.HACK_NO_XPCALL()
|
|
99
|
+
warn("HACK_NO_XPCALL is deprecated. It is now safe to yield in an " ..
|
|
100
|
+
"xpcall, so this is no longer necessary. It can be safely deleted.")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
env.fit = env.itFOCUS
|
|
104
|
+
env.xit = env.itSKIP
|
|
105
|
+
env.fdescribe = env.describeFOCUS
|
|
106
|
+
env.xdescribe = env.describeSKIP
|
|
107
|
+
|
|
108
|
+
env.expect = setmetatable({
|
|
109
|
+
extend = function(...)
|
|
110
|
+
error("Cannot call \"expect.extend\" from within a \"describe\" node.")
|
|
111
|
+
end,
|
|
112
|
+
}, {
|
|
113
|
+
__call = function(_self, ...)
|
|
114
|
+
return Expectation.new(...)
|
|
115
|
+
end,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return env
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
local TestNode = {}
|
|
122
|
+
TestNode.__index = TestNode
|
|
123
|
+
|
|
124
|
+
--[[
|
|
125
|
+
Create a new test node. A pointer to the test plan, a phrase to describe it
|
|
126
|
+
and the type of node it is are required. The modifier is optional and will
|
|
127
|
+
be None if left blank.
|
|
128
|
+
]]
|
|
129
|
+
function TestNode.new(plan, phrase, nodeType, nodeModifier)
|
|
130
|
+
nodeModifier = nodeModifier or TestEnum.NodeModifier.None
|
|
131
|
+
|
|
132
|
+
local node = {
|
|
133
|
+
plan = plan,
|
|
134
|
+
phrase = phrase,
|
|
135
|
+
type = nodeType,
|
|
136
|
+
modifier = nodeModifier,
|
|
137
|
+
children = {},
|
|
138
|
+
callback = nil,
|
|
139
|
+
parent = nil,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
node.environment = newEnvironment(node, plan.extraEnvironment)
|
|
143
|
+
return setmetatable(node, TestNode)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
local function getModifier(name, pattern, modifier)
|
|
147
|
+
if pattern and (modifier == nil or modifier == TestEnum.NodeModifier.None) then
|
|
148
|
+
if name:match(pattern) then
|
|
149
|
+
return TestEnum.NodeModifier.Focus
|
|
150
|
+
else
|
|
151
|
+
return TestEnum.NodeModifier.Skip
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
return modifier
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
function TestNode:addChild(phrase, nodeType, nodeModifier)
|
|
158
|
+
if nodeType == TestEnum.NodeType.It then
|
|
159
|
+
for _, child in pairs(self.children) do
|
|
160
|
+
if child.phrase == phrase then
|
|
161
|
+
error("Duplicate it block found: " .. child:getFullName())
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
local childName = self:getFullName() .. " " .. phrase
|
|
167
|
+
nodeModifier = getModifier(childName, self.plan.testNamePattern, nodeModifier)
|
|
168
|
+
local child = TestNode.new(self.plan, phrase, nodeType, nodeModifier)
|
|
169
|
+
child.parent = self
|
|
170
|
+
table.insert(self.children, child)
|
|
171
|
+
return child
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
--[[
|
|
175
|
+
Join the names of all the nodes back to the parent.
|
|
176
|
+
]]
|
|
177
|
+
function TestNode:getFullName()
|
|
178
|
+
if self.parent then
|
|
179
|
+
local parentPhrase = self.parent:getFullName()
|
|
180
|
+
if parentPhrase then
|
|
181
|
+
return parentPhrase .. " " .. self.phrase
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
return self.phrase
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
--[[
|
|
188
|
+
Expand a node by setting its callback environment and then calling it. Any
|
|
189
|
+
further it and describe calls within the callback will be added to the tree.
|
|
190
|
+
]]
|
|
191
|
+
function TestNode:expand()
|
|
192
|
+
local originalEnv = getfenv(self.callback)
|
|
193
|
+
local callbackEnv = setmetatable({}, { __index = originalEnv })
|
|
194
|
+
for key, value in pairs(self.environment) do
|
|
195
|
+
callbackEnv[key] = value
|
|
196
|
+
end
|
|
197
|
+
-- Copy 'script' directly to new env to make Studio debugger happy.
|
|
198
|
+
-- Studio debugger does not look into __index, because of security reasons
|
|
199
|
+
callbackEnv.script = originalEnv.script
|
|
200
|
+
setfenv(self.callback, callbackEnv)
|
|
201
|
+
|
|
202
|
+
local success, result = xpcall(self.callback, function(message)
|
|
203
|
+
return debug.traceback(tostring(message), 2)
|
|
204
|
+
end)
|
|
205
|
+
|
|
206
|
+
if not success then
|
|
207
|
+
self.loadError = result
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
local TestPlan = {}
|
|
212
|
+
TestPlan.__index = TestPlan
|
|
213
|
+
|
|
214
|
+
--[[
|
|
215
|
+
Create a new, empty TestPlan.
|
|
216
|
+
]]
|
|
217
|
+
function TestPlan.new(testNamePattern, extraEnvironment)
|
|
218
|
+
local plan = {
|
|
219
|
+
children = {},
|
|
220
|
+
testNamePattern = testNamePattern,
|
|
221
|
+
extraEnvironment = extraEnvironment,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return setmetatable(plan, TestPlan)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
--[[
|
|
228
|
+
Add a new child under the test plan's root node.
|
|
229
|
+
]]
|
|
230
|
+
function TestPlan:addChild(phrase, nodeType, nodeModifier)
|
|
231
|
+
nodeModifier = getModifier(phrase, self.testNamePattern, nodeModifier)
|
|
232
|
+
local child = TestNode.new(self, phrase, nodeType, nodeModifier)
|
|
233
|
+
table.insert(self.children, child)
|
|
234
|
+
return child
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
--[[
|
|
238
|
+
Add a new describe node with the given method as a callback. Generates or
|
|
239
|
+
reuses all the describe nodes along the path.
|
|
240
|
+
]]
|
|
241
|
+
function TestPlan:addRoot(path, method)
|
|
242
|
+
local curNode = self
|
|
243
|
+
for i = #path, 1, -1 do
|
|
244
|
+
local nextNode = nil
|
|
245
|
+
|
|
246
|
+
for _, child in ipairs(curNode.children) do
|
|
247
|
+
if child.phrase == path[i] then
|
|
248
|
+
nextNode = child
|
|
249
|
+
break
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
if nextNode == nil then
|
|
254
|
+
nextNode = curNode:addChild(path[i], TestEnum.NodeType.Describe)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
curNode = nextNode
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
curNode.callback = method
|
|
261
|
+
curNode:expand()
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
--[[
|
|
265
|
+
Calls the given callback on all nodes in the tree, traversed depth-first.
|
|
266
|
+
]]
|
|
267
|
+
function TestPlan:visitAllNodes(callback, root, level)
|
|
268
|
+
root = root or self
|
|
269
|
+
level = level or 0
|
|
270
|
+
|
|
271
|
+
for _, child in ipairs(root.children) do
|
|
272
|
+
callback(child, level)
|
|
273
|
+
|
|
274
|
+
self:visitAllNodes(callback, child, level + 1)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
--[[
|
|
279
|
+
Visualizes the test plan in a simple format, suitable for debugging the test
|
|
280
|
+
plan's structure.
|
|
281
|
+
]]
|
|
282
|
+
function TestPlan:visualize()
|
|
283
|
+
local buffer = {}
|
|
284
|
+
self:visitAllNodes(function(node, level)
|
|
285
|
+
table.insert(buffer, (" "):rep(3 * level) .. node.phrase)
|
|
286
|
+
end)
|
|
287
|
+
return table.concat(buffer, "\n")
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
--[[
|
|
291
|
+
Gets a list of all nodes in the tree for which the given callback returns
|
|
292
|
+
true.
|
|
293
|
+
]]
|
|
294
|
+
function TestPlan:findNodes(callback)
|
|
295
|
+
local results = {}
|
|
296
|
+
self:visitAllNodes(function(node)
|
|
297
|
+
if callback(node) then
|
|
298
|
+
table.insert(results, node)
|
|
299
|
+
end
|
|
300
|
+
end)
|
|
301
|
+
return results
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
return TestPlan
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Turns a series of specification functions into a test plan.
|
|
3
|
+
|
|
4
|
+
Uses a TestPlanBuilder to keep track of the state of the tree being built.
|
|
5
|
+
]]
|
|
6
|
+
local TestPlan = require(script.Parent.TestPlan)
|
|
7
|
+
|
|
8
|
+
local TestPlanner = {}
|
|
9
|
+
|
|
10
|
+
--[[
|
|
11
|
+
Create a new TestPlan from a list of specification functions.
|
|
12
|
+
|
|
13
|
+
These functions should call a combination of `describe` and `it` (and their
|
|
14
|
+
variants), which will be turned into a test plan to be executed.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
- modulesList - list of tables describing test modules {
|
|
18
|
+
method, -- specification function described above
|
|
19
|
+
path, -- array of parent entires, first element is the leaf that owns `method`
|
|
20
|
+
pathStringForSorting -- a string representation of `path`, used for sorting of the test plan
|
|
21
|
+
}
|
|
22
|
+
- testNamePattern - Only tests matching this Lua pattern string will run. Pass empty or nil to run all tests
|
|
23
|
+
- extraEnvironment - Lua table holding additional functions and variables to be injected into the specification
|
|
24
|
+
function during execution
|
|
25
|
+
]]
|
|
26
|
+
function TestPlanner.createPlan(modulesList, testNamePattern, extraEnvironment)
|
|
27
|
+
local plan = TestPlan.new(testNamePattern, extraEnvironment)
|
|
28
|
+
|
|
29
|
+
table.sort(modulesList, function(a, b)
|
|
30
|
+
return a.pathStringForSorting < b.pathStringForSorting
|
|
31
|
+
end)
|
|
32
|
+
|
|
33
|
+
for _, module in ipairs(modulesList) do
|
|
34
|
+
plan:addRoot(module.path, module.method)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
return plan
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
return TestPlanner
|