mirror of
https://github.com/imezx/Warp.git
synced 2025-04-24 15:10:03 +00:00
188 lines
4.9 KiB
Lua
188 lines
4.9 KiB
Lua
--[[
|
|
Contains the logic to run a test plan and gather test results from it.
|
|
|
|
TestRunner accepts a TestPlan object, executes the planned tests, and
|
|
produces a TestResults object. While the tests are running, the system's
|
|
state is contained inside a TestSession object.
|
|
]]
|
|
|
|
local TestEnum = require(script.Parent.TestEnum)
|
|
local TestSession = require(script.Parent.TestSession)
|
|
local LifecycleHooks = require(script.Parent.LifecycleHooks)
|
|
|
|
local RUNNING_GLOBAL = "__TESTEZ_RUNNING_TEST__"
|
|
|
|
local TestRunner = {
|
|
environment = {}
|
|
}
|
|
|
|
local function wrapExpectContextWithPublicApi(expectationContext)
|
|
return setmetatable({
|
|
extend = function(...)
|
|
expectationContext:extend(...)
|
|
end,
|
|
}, {
|
|
__call = function(_self, ...)
|
|
return expectationContext:startExpectationChain(...)
|
|
end,
|
|
})
|
|
end
|
|
|
|
--[[
|
|
Runs the given TestPlan and returns a TestResults object representing the
|
|
results of the run.
|
|
]]
|
|
function TestRunner.runPlan(plan)
|
|
local session = TestSession.new(plan)
|
|
local lifecycleHooks = LifecycleHooks.new()
|
|
|
|
local exclusiveNodes = plan:findNodes(function(node)
|
|
return node.modifier == TestEnum.NodeModifier.Focus
|
|
end)
|
|
|
|
session.hasFocusNodes = #exclusiveNodes > 0
|
|
|
|
TestRunner.runPlanNode(session, plan, lifecycleHooks)
|
|
|
|
return session:finalize()
|
|
end
|
|
|
|
--[[
|
|
Run the given test plan node and its descendants, using the given test
|
|
session to store all of the results.
|
|
]]
|
|
function TestRunner.runPlanNode(session, planNode, lifecycleHooks)
|
|
local function runCallback(callback, messagePrefix)
|
|
local success = true
|
|
local errorMessage
|
|
-- Any code can check RUNNING_GLOBAL to fork behavior based on
|
|
-- whether a test is running. We use this to avoid accessing
|
|
-- protected APIs; it's a workaround that will go away someday.
|
|
_G[RUNNING_GLOBAL] = true
|
|
|
|
messagePrefix = messagePrefix or ""
|
|
|
|
local testEnvironment = getfenv(callback)
|
|
|
|
for key, value in pairs(TestRunner.environment) do
|
|
testEnvironment[key] = value
|
|
end
|
|
|
|
testEnvironment.fail = function(message)
|
|
if message == nil then
|
|
message = "fail() was called."
|
|
end
|
|
|
|
success = false
|
|
errorMessage = messagePrefix .. debug.traceback(tostring(message), 2)
|
|
end
|
|
|
|
testEnvironment.expect = wrapExpectContextWithPublicApi(session:getExpectationContext())
|
|
|
|
local context = session:getContext()
|
|
|
|
local nodeSuccess, nodeResult = xpcall(
|
|
function()
|
|
callback(context)
|
|
end,
|
|
function(message)
|
|
return messagePrefix .. debug.traceback(tostring(message), 2)
|
|
end
|
|
)
|
|
|
|
-- If a node threw an error, we prefer to use that message over
|
|
-- one created by fail() if it was set.
|
|
if not nodeSuccess then
|
|
success = false
|
|
errorMessage = nodeResult
|
|
end
|
|
|
|
_G[RUNNING_GLOBAL] = nil
|
|
|
|
return success, errorMessage
|
|
end
|
|
|
|
local function runNode(childPlanNode)
|
|
-- Errors can be set either via `error` propagating upwards or
|
|
-- by a test calling fail([message]).
|
|
|
|
for _, hook in ipairs(lifecycleHooks:getBeforeEachHooks()) do
|
|
local success, errorMessage = runCallback(hook, "beforeEach hook: ")
|
|
if not success then
|
|
return false, errorMessage
|
|
end
|
|
end
|
|
|
|
local testSuccess, testErrorMessage = runCallback(childPlanNode.callback)
|
|
|
|
for _, hook in ipairs(lifecycleHooks:getAfterEachHooks()) do
|
|
local success, errorMessage = runCallback(hook, "afterEach hook: ")
|
|
if not success then
|
|
if not testSuccess then
|
|
return false, testErrorMessage .. "\nWhile cleaning up the failed test another error was found:\n" .. errorMessage
|
|
end
|
|
return false, errorMessage
|
|
end
|
|
end
|
|
|
|
if not testSuccess then
|
|
return false, testErrorMessage
|
|
end
|
|
|
|
return true, nil
|
|
end
|
|
|
|
lifecycleHooks:pushHooksFrom(planNode)
|
|
|
|
local halt = false
|
|
for _, hook in ipairs(lifecycleHooks:getBeforeAllHooks()) do
|
|
local success, errorMessage = runCallback(hook, "beforeAll hook: ")
|
|
if not success then
|
|
session:addDummyError("beforeAll", errorMessage)
|
|
halt = true
|
|
end
|
|
end
|
|
|
|
if not halt then
|
|
for _, childPlanNode in ipairs(planNode.children) do
|
|
if childPlanNode.type == TestEnum.NodeType.It then
|
|
session:pushNode(childPlanNode)
|
|
if session:shouldSkip() then
|
|
session:setSkipped()
|
|
else
|
|
local success, errorMessage = runNode(childPlanNode)
|
|
|
|
if success then
|
|
session:setSuccess()
|
|
else
|
|
session:setError(errorMessage)
|
|
end
|
|
end
|
|
session:popNode()
|
|
elseif childPlanNode.type == TestEnum.NodeType.Describe then
|
|
session:pushNode(childPlanNode)
|
|
TestRunner.runPlanNode(session, childPlanNode, lifecycleHooks)
|
|
|
|
-- Did we have an error trying build a test plan?
|
|
if childPlanNode.loadError then
|
|
local message = "Error during planning: " .. childPlanNode.loadError
|
|
session:setError(message)
|
|
else
|
|
session:setStatusFromChildren()
|
|
end
|
|
session:popNode()
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, hook in ipairs(lifecycleHooks:getAfterAllHooks()) do
|
|
local success, errorMessage = runCallback(hook, "afterAll hook: ")
|
|
if not success then
|
|
session:addDummyError("afterAll", errorMessage)
|
|
end
|
|
end
|
|
|
|
lifecycleHooks:popHooks()
|
|
end
|
|
|
|
return TestRunner
|