add gs
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
--[[
|
||||
Turns a series of specification functions into a test plan.
|
||||
|
||||
Uses a TestPlanBuilder to keep track of the state of the tree being built.
|
||||
]]
|
||||
|
||||
local TestEnum = require(script.Parent.TestEnum)
|
||||
local TestPlanBuilder = require(script.Parent.TestPlanBuilder)
|
||||
|
||||
local TestPlanner = {}
|
||||
|
||||
local function buildPlan(builder, module, env)
|
||||
local currentEnv = getfenv(module.method)
|
||||
|
||||
for key, value in pairs(env) do
|
||||
currentEnv[key] = value
|
||||
end
|
||||
|
||||
local nodeCount = #module.path
|
||||
|
||||
-- Dive into auto-named nodes for this module
|
||||
for i = nodeCount, 1, -1 do
|
||||
local name = module.path[i]
|
||||
builder:pushNode(name, TestEnum.NodeType.Describe)
|
||||
end
|
||||
|
||||
local ok, err = xpcall(module.method, function(err)
|
||||
return err .. "\n" .. debug.traceback()
|
||||
end)
|
||||
|
||||
-- This is an error outside of any describe/it blocks.
|
||||
-- We attach it to the node we generate automatically per-file.
|
||||
if not ok then
|
||||
local node = builder:getCurrentNode()
|
||||
node.loadError = err
|
||||
end
|
||||
|
||||
-- Back out of auto-named nodes
|
||||
for _ = 1, nodeCount do
|
||||
builder:popNode()
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a new environment with functions for defining the test plan structure
|
||||
using the given TestPlanBuilder.
|
||||
|
||||
These functions illustrate the advantage of the stack-style tree navigation
|
||||
as state doesn't need to be passed around between functions or explicitly
|
||||
global.
|
||||
]]
|
||||
function TestPlanner.createEnvironment(builder)
|
||||
local env = {}
|
||||
|
||||
function env.describeFOCUS(phrase, callback)
|
||||
return env.describe(phrase, callback, TestEnum.NodeModifier.Focus)
|
||||
end
|
||||
|
||||
function env.describeSKIP(phrase, callback)
|
||||
return env.describe(phrase, callback, TestEnum.NodeModifier.Skip)
|
||||
end
|
||||
|
||||
function env.describe(phrase, callback, nodeModifier)
|
||||
local node = builder:pushNode(phrase, TestEnum.NodeType.Describe, nodeModifier)
|
||||
|
||||
local ok, err = pcall(callback)
|
||||
|
||||
-- loadError on a TestPlan node is an automatic failure
|
||||
if not ok then
|
||||
node.loadError = err
|
||||
end
|
||||
|
||||
builder:popNode()
|
||||
end
|
||||
|
||||
function env.try(phrase, callback)
|
||||
local node = builder:pushNode(phrase, TestEnum.NodeType.Try)
|
||||
|
||||
local ok, err = pcall(callback)
|
||||
|
||||
-- loadError on a TestPlan node is an automatic failure
|
||||
if not ok then
|
||||
node.loadError = err
|
||||
end
|
||||
|
||||
builder:popNode()
|
||||
end
|
||||
|
||||
function env.it(phrase, callback)
|
||||
local node = builder:pushNode(phrase, TestEnum.NodeType.It)
|
||||
|
||||
node.callback = callback
|
||||
|
||||
builder:popNode()
|
||||
end
|
||||
|
||||
function env.itFOCUS(phrase, callback)
|
||||
local node = builder:pushNode(phrase, TestEnum.NodeType.It, TestEnum.NodeModifier.Focus)
|
||||
|
||||
node.callback = callback
|
||||
|
||||
builder:popNode()
|
||||
end
|
||||
|
||||
function env.itSKIP(phrase, callback)
|
||||
local node = builder:pushNode(phrase, TestEnum.NodeType.It, TestEnum.NodeModifier.Skip)
|
||||
|
||||
node.callback = callback
|
||||
|
||||
builder:popNode()
|
||||
end
|
||||
|
||||
function env.itFIXME(phrase, callback)
|
||||
local node = builder:pushNode(phrase, TestEnum.NodeType.It, TestEnum.NodeModifier.Skip)
|
||||
|
||||
warn("FIXME: broken test", node:getFullName())
|
||||
node.callback = callback
|
||||
|
||||
builder:popNode()
|
||||
end
|
||||
|
||||
function env.FIXME(optionalMessage)
|
||||
local currentNode = builder:getCurrentNode()
|
||||
warn("FIXME: broken test", currentNode:getFullName(), optionalMessage or "")
|
||||
|
||||
currentNode.modifier = TestEnum.NodeModifier.Skip
|
||||
end
|
||||
|
||||
function env.FOCUS()
|
||||
local currentNode = builder:getCurrentNode()
|
||||
|
||||
currentNode.modifier = TestEnum.NodeModifier.Focus
|
||||
end
|
||||
|
||||
function env.SKIP()
|
||||
local currentNode = builder:getCurrentNode()
|
||||
|
||||
currentNode.modifier = TestEnum.NodeModifier.Skip
|
||||
end
|
||||
|
||||
--[[
|
||||
These method is intended to disable the use of xpcall when running
|
||||
nodes contained in the same node that this function is called in.
|
||||
This is because xpcall breaks badly if the method passed yields.
|
||||
|
||||
This function is intended to be hideous and seldom called.
|
||||
|
||||
Once xpcall is able to yield, this function is obsolete.
|
||||
]]
|
||||
function env.HACK_NO_XPCALL()
|
||||
local currentNode = builder:getCurrentNode()
|
||||
|
||||
currentNode.HACK_NO_XPCALL = true
|
||||
end
|
||||
|
||||
env.step = env.it
|
||||
|
||||
env.fit = env.itFOCUS
|
||||
env.xit = env.itSKIP
|
||||
env.fdescribe = env.describeFOCUS
|
||||
env.xdescribe = env.describeSKIP
|
||||
|
||||
function env.include(...)
|
||||
local args = {...}
|
||||
local method, path
|
||||
if #args == 1 then
|
||||
method = args[1]
|
||||
path = {}
|
||||
elseif #args == 2 then
|
||||
method = args[2]
|
||||
path = {args[1]}
|
||||
end
|
||||
buildPlan(builder, {path = path, method = method}, env)
|
||||
end
|
||||
|
||||
return env
|
||||
end
|
||||
|
||||
--[[
|
||||
Create a new TestPlan from a list of specification functions.
|
||||
|
||||
These functions should call a combination of `describe` and `it` (and their
|
||||
variants), which will be turned into a test plan to be executed.
|
||||
]]
|
||||
function TestPlanner.createPlan(specFunctions, noXpcallByDefault, testNamePattern)
|
||||
local builder = TestPlanBuilder.new()
|
||||
builder.noXpcallByDefault = noXpcallByDefault
|
||||
builder.testNamePattern = testNamePattern
|
||||
local env = TestPlanner.createEnvironment(builder)
|
||||
|
||||
for _, module in ipairs(specFunctions) do
|
||||
buildPlan(builder, module, env)
|
||||
end
|
||||
|
||||
return builder:finalize()
|
||||
end
|
||||
|
||||
return TestPlanner
|
||||
Reference in New Issue
Block a user