--[[ knife.test - A fixture-free test framework. https://github.com/airstruck/knife The MIT License (MIT) Copyright (c) 2015 airstruck Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] local test, testAssert, testError -- Create a node representing a test section local function createNode (parent, description, process) return setmetatable({ parent = parent, description = description, process = process, nodes = {}, activeNodeIndex = 1, currentNodeIndex = 0, assert = testAssert, error = testError, }, { __call = test }) end -- Run a node local function runNode (node) node.currentNodeIndex = 0 return node:process() end -- Get the root node for a given node local function getRootNode (node) local parent = node.parent return parent and getRootNode(parent) or node end -- Update the active child node of the given node local function updateActiveNode (node, description, process) local activeNodeIndex = node.activeNodeIndex local nodes = node.nodes local activeNode = nodes[activeNodeIndex] if not activeNode then activeNode = createNode(node, description, process) nodes[activeNodeIndex] = activeNode else activeNode.process = process end getRootNode(node).lastActiveLeaf = activeNode return activeNode end -- Run the active child node of the given node local function runActiveNode (node, description, process) local activeNode = updateActiveNode(node, description, process) return runNode(activeNode) end -- Get ancestors of a node, including the node local function getAncestors (node) local ancestors = { node } for ancestor in function () return node.parent end do ancestors[#ancestors + 1] = ancestor node = ancestor end return ancestors end -- Print a message describing one execution path in the test scenario local function printScenario (node) local ancestors = getAncestors(node) for i = #ancestors, 1, -1 do io.stderr:write(ancestors[i].description or '') io.stderr:write('\n') end end -- Print a message and stop the test scenario when an assertion fails local function failAssert (node, description, message) io.stderr:write(message or '') io.stderr:write('\n\n') printScenario(node) io.stderr:write(description or '') io.stderr:write('\n\n') error(message or '', 2) end -- Create a branch node for a test scenario test = function (node, description, process) node.currentNodeIndex = node.currentNodeIndex + 1 if node.currentNodeIndex == node.activeNodeIndex then return runActiveNode(node, description, process) end end -- Test an assertion testAssert = function (self, value, description) if not value then return failAssert(self, description, 'Test failed: assertion failed') end return value end -- Expect function f to fail testError = function (self, f, description) if pcall(f) then return failAssert(self, description, 'Test failed: expected error') end end -- Create the root node for a test scenario local function T (description, process) local root = createNode(nil, description, process) runNode(root) while root.activeNodeIndex <= #root.nodes do local lastActiveBranch = root.lastActiveLeaf.parent lastActiveBranch.activeNodeIndex = lastActiveBranch.activeNodeIndex + 1 runNode(root) end return root end -- Run any other files passed from CLI. if arg and arg[0] and arg[0]:gmatch('test.lua') then _G.T = T for i = 1, #arg do dofile(arg[i]) end _G.T = nil end return T