add gs
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
local Enums = {
|
||||
Type = {
|
||||
Describe = "Describe",
|
||||
Step = "Step",
|
||||
},
|
||||
Modifier = {
|
||||
Protect = "Protect",
|
||||
Skip = "Skip"
|
||||
},
|
||||
}
|
||||
|
||||
return Enums
|
||||
@@ -0,0 +1,89 @@
|
||||
local TestService = game:GetService("TestService")
|
||||
local Enums = require(script.Parent.Parent.Enums)
|
||||
local TeamCityReporter = {}
|
||||
|
||||
local function teamCityEscape(str)
|
||||
str = string.gsub(str, "([]|'[])","|%1")
|
||||
str = string.gsub(str, "\r", "|r")
|
||||
str = string.gsub(str, "\n", "|n")
|
||||
return str
|
||||
end
|
||||
|
||||
local function teamCityEnterSuite(suiteName)
|
||||
return string.format("##teamcity[testSuiteStarted name='%s']", teamCityEscape(suiteName))
|
||||
end
|
||||
|
||||
local function teamCityLeaveSuite(suiteName)
|
||||
return string.format("##teamcity[testSuiteFinished name='%s']", teamCityEscape(suiteName))
|
||||
end
|
||||
|
||||
local function teamCityEnterCase(caseName)
|
||||
return string.format("##teamcity[testStarted name='%s']", teamCityEscape(caseName))
|
||||
end
|
||||
|
||||
local function teamCityLeaveCase(caseName)
|
||||
return string.format("##teamcity[testFinished name='%s']", teamCityEscape(caseName))
|
||||
end
|
||||
|
||||
local function teamCityFailCase(caseName, errorMessage)
|
||||
return string.format("##teamcity[testFailed name='%s' message='%s']",
|
||||
teamCityEscape(caseName), teamCityEscape(errorMessage))
|
||||
end
|
||||
|
||||
local function reportNode(node, buffer, level)
|
||||
buffer = buffer or {}
|
||||
level = level or 0
|
||||
if not node.testTouched then
|
||||
return buffer
|
||||
end
|
||||
if node.type == Enums.Type.Describe then
|
||||
table.insert(buffer, teamCityEnterSuite(node.text))
|
||||
for _, child in ipairs(node.children) do
|
||||
reportNode(child, buffer, level + 1)
|
||||
end
|
||||
table.insert(buffer, teamCityLeaveSuite(node.text))
|
||||
elseif node.type == Enums.Type.Step then
|
||||
table.insert(buffer, teamCityEnterCase(node.text))
|
||||
if not node.testSuccess then
|
||||
table.insert(buffer, teamCityFailCase(node.text, node.errorMessage))
|
||||
end
|
||||
table.insert(buffer, teamCityLeaveCase(node.text))
|
||||
end
|
||||
end
|
||||
|
||||
local function treeToString(testNode)
|
||||
local result = {}
|
||||
for _, child in ipairs(testNode.children) do
|
||||
reportNode(child, result)
|
||||
end
|
||||
return table.concat(result, "\n")
|
||||
end
|
||||
|
||||
function TeamCityReporter.report(results)
|
||||
local resultBuffer = {
|
||||
"Test results:",
|
||||
treeToString(results.testNode),
|
||||
string.format(
|
||||
"%d passed, %d failed, %d skipped",
|
||||
results.successCount,
|
||||
results.failureCount,
|
||||
results.skippedCount
|
||||
)
|
||||
}
|
||||
print(table.concat(resultBuffer, "\n"))
|
||||
|
||||
if results.failureCount > 0 then
|
||||
print(("%d test nodes reported failures."):format(results.failureCount))
|
||||
end
|
||||
|
||||
if #results.errors > 0 then
|
||||
print("Errors reported by tests:\n")
|
||||
|
||||
for _, message in ipairs(results.errors) do
|
||||
TestService:Error(message)
|
||||
print("")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return TeamCityReporter
|
||||
@@ -0,0 +1,49 @@
|
||||
local TestService = game:GetService("TestService")
|
||||
local TextReporter = {}
|
||||
local INDENT = (" "):rep(3)
|
||||
|
||||
local function treeToString(testNode)
|
||||
local result = {}
|
||||
|
||||
local function callback(node, level)
|
||||
local symbol = node.testTouched and (node.testSuccess and "+" or "-") or "~"
|
||||
local line = ("%s[%s] %s"):format(
|
||||
INDENT:rep(level),
|
||||
symbol,
|
||||
node.text
|
||||
)
|
||||
table.insert(result, line)
|
||||
end
|
||||
|
||||
testNode:visit(callback)
|
||||
return table.concat(result, "\n")
|
||||
end
|
||||
|
||||
function TextReporter.report(results)
|
||||
local resultBuffer = {
|
||||
"Test results:",
|
||||
treeToString(results.testNode),
|
||||
string.format(
|
||||
"%d passed, %d failed, %d skipped",
|
||||
results.successCount,
|
||||
results.failureCount,
|
||||
results.skippedCount
|
||||
)
|
||||
}
|
||||
print(table.concat(resultBuffer, "\n"))
|
||||
|
||||
if results.failureCount > 0 then
|
||||
print(("%d test nodes reported failures."):format(results.failureCount))
|
||||
end
|
||||
|
||||
if #results.errors > 0 then
|
||||
print("Errors reported by tests:\n")
|
||||
|
||||
for _, message in ipairs(results.errors) do
|
||||
TestService:Error(message)
|
||||
print("")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return TextReporter
|
||||
@@ -0,0 +1,39 @@
|
||||
local TestNode = {}
|
||||
TestNode.__index = TestNode
|
||||
|
||||
function TestNode.new(text, type, modifiers)
|
||||
local self = {
|
||||
parent = nil,
|
||||
children = {},
|
||||
|
||||
text = text,
|
||||
type = type,
|
||||
modifiers = modifiers or {},
|
||||
callback = nil,
|
||||
testSuccess = true,
|
||||
testTouched = false,
|
||||
loadSuccess = true,
|
||||
errorMessage = "",
|
||||
}
|
||||
setmetatable(self, TestNode)
|
||||
return self
|
||||
end
|
||||
|
||||
function TestNode:addChild(node)
|
||||
node.parent = self
|
||||
table.insert(self.children, node)
|
||||
end
|
||||
|
||||
function TestNode:visit(callback, level)
|
||||
level = level or 0
|
||||
callback(self, level);
|
||||
for _, child in ipairs(self.children) do
|
||||
child:visit(callback, level + 1)
|
||||
end
|
||||
end
|
||||
|
||||
function TestNode:isRoot()
|
||||
return self.parent == nil
|
||||
end
|
||||
|
||||
return TestNode
|
||||
@@ -0,0 +1,70 @@
|
||||
local Enums = require(script.Parent.Enums)
|
||||
local TestNode = require(script.Parent.TestNode)
|
||||
local createModdableFunction = require(script.Parent.createModdableFunction)
|
||||
|
||||
local TestPlanner = {}
|
||||
TestPlanner.__index = TestPlanner
|
||||
|
||||
local function doPlan(currentNode, method, env)
|
||||
local currentEnv = getfenv(method)
|
||||
for key, value in pairs(env) do
|
||||
currentEnv[key] = value
|
||||
end
|
||||
|
||||
local success, result = xpcall(method, function(err)
|
||||
return err .. "\n" .. debug.traceback()
|
||||
end)
|
||||
|
||||
if not success then
|
||||
currentNode.loadSuccess = false
|
||||
currentNode.errorMessage = result
|
||||
end
|
||||
end
|
||||
|
||||
local validModifiers = {
|
||||
protected = Enums.Modifier.Protect,
|
||||
skipped = Enums.Modifier.Skip,
|
||||
}
|
||||
|
||||
local function createEnv(currentNode)
|
||||
local function describe(modifiers, text, callback)
|
||||
local testNode = TestNode.new(text, Enums.Type.Describe, modifiers)
|
||||
currentNode:addChild(testNode)
|
||||
currentNode = testNode
|
||||
local success, result = xpcall(callback, function(err)
|
||||
return err .. "\n" .. debug.traceback()
|
||||
end)
|
||||
if not success then
|
||||
testNode.loadSuccess = false
|
||||
testNode.errorMessage = result
|
||||
end
|
||||
currentNode = currentNode.parent
|
||||
end
|
||||
|
||||
local function step(modifiers, text, callback)
|
||||
local testNode = TestNode.new(text, Enums.Type.Step, modifiers)
|
||||
testNode.callback = callback
|
||||
currentNode:addChild(testNode)
|
||||
end
|
||||
|
||||
local env = {}
|
||||
env.describe = createModdableFunction(validModifiers, describe)
|
||||
env.step = createModdableFunction(validModifiers, step)
|
||||
env.skip = function()
|
||||
currentNode.modifiers[Enums.Modifier.Skip] = true
|
||||
end
|
||||
env.include = function(method)
|
||||
doPlan(currentNode, method, env)
|
||||
end
|
||||
return env
|
||||
end
|
||||
|
||||
function TestPlanner.plan(method)
|
||||
local currentNode = TestNode.new("Root", Enums.Type.Describe)
|
||||
local env = createEnv(currentNode)
|
||||
doPlan(currentNode, method, env)
|
||||
|
||||
return currentNode
|
||||
end
|
||||
|
||||
return TestPlanner
|
||||
@@ -0,0 +1,36 @@
|
||||
local Enums = require(script.Parent.Enums)
|
||||
|
||||
local TestResult = {}
|
||||
TestResult.__index = TestResult
|
||||
|
||||
function TestResult.getResult(testNode)
|
||||
local result = {
|
||||
testNode = testNode,
|
||||
successCount = 0,
|
||||
failureCount = 0,
|
||||
skippedCount = 0,
|
||||
errors = {}
|
||||
}
|
||||
|
||||
local function callback(node, level)
|
||||
if node.type == Enums.Type.Describe then
|
||||
if not node.loadSuccess then
|
||||
result.failureCount = result.failureCount + 1
|
||||
table.insert(result.errors, node.errorMessage)
|
||||
end
|
||||
elseif node.type == Enums.Type.Step then
|
||||
if not node.testTouched then
|
||||
result.skippedCount = result.skippedCount + 1
|
||||
elseif node.testSuccess then
|
||||
result.successCount = result.successCount + 1
|
||||
else
|
||||
result.failureCount = result.failureCount + 1
|
||||
table.insert(result.errors, node.errorMessage)
|
||||
end
|
||||
end
|
||||
end
|
||||
testNode:visit(callback)
|
||||
return result
|
||||
end
|
||||
|
||||
return TestResult
|
||||
@@ -0,0 +1,72 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Enums = require(script.Parent.Enums)
|
||||
local Expectation = require(CorePackages.TestEZ).Expectation
|
||||
|
||||
local TestRunner = {}
|
||||
|
||||
local testEnv = {expect = Expectation.new}
|
||||
|
||||
local function assign(to, from)
|
||||
for key, value in pairs(from) do
|
||||
to[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
local function setTestEnv(method)
|
||||
assign(getfenv(method), testEnv)
|
||||
end
|
||||
|
||||
local function run(testNode, protectionState)
|
||||
if testNode.modifiers[Enums.Modifier.Skip] then
|
||||
return
|
||||
end
|
||||
testNode.testTouched = true
|
||||
|
||||
if testNode.modifiers[Enums.Modifier.Protect] then
|
||||
-- Update the protection value so that descendant nodes will reference this value
|
||||
protectionState = {
|
||||
failedNode = protectionState.failedNode
|
||||
}
|
||||
end
|
||||
|
||||
if testNode.type == Enums.Type.Describe then
|
||||
for _, child in ipairs(testNode.children) do
|
||||
run(child, protectionState)
|
||||
end
|
||||
elseif testNode.type == Enums.Type.Step then
|
||||
if protectionState and protectionState.failedNode then
|
||||
testNode.testSuccess = false
|
||||
testNode.errorMessage = string.format(
|
||||
"%q failed without execution, because %q failed",
|
||||
testNode.text, protectionState.failedNode.text
|
||||
)
|
||||
else
|
||||
setTestEnv(testNode.callback)
|
||||
local success, result = pcall(testNode.callback)
|
||||
if not success then
|
||||
testNode.testSuccess = false
|
||||
testNode.errorMessage = result
|
||||
protectionState.failedNode = testNode
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not testNode.loadSuccess then
|
||||
testNode.errorMessage = string.format("Error during planning: %q\n%s", testNode.text, testNode.errorMessage)
|
||||
testNode.testSuccess = false
|
||||
end
|
||||
|
||||
if testNode.parent and not testNode.testSuccess then
|
||||
testNode.parent.testSuccess = false
|
||||
end
|
||||
end
|
||||
|
||||
function TestRunner.run(testNode)
|
||||
-- protected by default, which means when one step failed, all steps after will be skipped by default
|
||||
run(testNode, {
|
||||
failedNode = nil
|
||||
})
|
||||
return testNode
|
||||
end
|
||||
|
||||
return TestRunner
|
||||
@@ -0,0 +1,39 @@
|
||||
--[[createModdableFunction can be used to build a Moddable function in a chain style
|
||||
for example:
|
||||
validModifiers = {
|
||||
protected = Enums.Modifier.Protect,
|
||||
skipped = Enums.Modifier.Skip,
|
||||
}
|
||||
describe = createModdableFunction(validModifiers, callback)
|
||||
describe.protected.skipped("description", function() end)
|
||||
step.skipped("description", function() end)
|
||||
]]
|
||||
|
||||
local function createModdableFunction(validModifiers, method, appliedModifiers)
|
||||
if appliedModifiers == nil then
|
||||
appliedModifiers = {}
|
||||
end
|
||||
|
||||
return setmetatable({}, {
|
||||
__call = function(_, ...)
|
||||
return method(appliedModifiers, ...)
|
||||
end,
|
||||
__index = function(_, key)
|
||||
if not validModifiers[key] then
|
||||
error(("%q is not a valid modifier"):format(tostring(key)), 2)
|
||||
end
|
||||
|
||||
local newAppliedModifiers = {}
|
||||
|
||||
for key, value in pairs(appliedModifiers) do
|
||||
newAppliedModifiers[key] = value
|
||||
end
|
||||
|
||||
newAppliedModifiers[validModifiers[key]] = true
|
||||
|
||||
return createModdableFunction(validModifiers, method, newAppliedModifiers)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return createModdableFunction
|
||||
@@ -0,0 +1,12 @@
|
||||
local TestPlanner = require(script.Parent.TestPlanner)
|
||||
local TestRunner = require(script.Parent.TestRunner)
|
||||
local TestResult = require(script.Parent.TestResult)
|
||||
|
||||
local function startTest(method, reporter)
|
||||
local testNode = TestPlanner.plan(method)
|
||||
testNode = TestRunner.run(testNode)
|
||||
local result = TestResult.getResult(testNode)
|
||||
reporter.report(result)
|
||||
end
|
||||
|
||||
return startTest
|
||||
Reference in New Issue
Block a user