From 6c36a862a24f4b2d858f991f2582a8c8603e2da1 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Thu, 7 May 2020 13:58:46 -0700 Subject: [PATCH 01/20] Collapse TestPlan/TestPlanner/TestPlanBuilder/TestEnvironment --- src/TestEnvironment.lua | 144 ----------------------- src/TestPlan.lua | 244 +++++++++++++++++++++++++++++++++------ src/TestPlanBuilder.lua | 96 --------------- src/TestPlanner.lua | 45 +------- src/init.lua | 2 - tests/lifecycleHooks.lua | 4 + 6 files changed, 219 insertions(+), 316 deletions(-) delete mode 100644 src/TestEnvironment.lua delete mode 100644 src/TestPlanBuilder.lua diff --git a/src/TestEnvironment.lua b/src/TestEnvironment.lua deleted file mode 100644 index f2433e8..0000000 --- a/src/TestEnvironment.lua +++ /dev/null @@ -1,144 +0,0 @@ ---[[ - 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. -]] -local TestEnum = require(script.Parent.TestEnum) - -local TestEnvironment = {} - -function TestEnvironment.new(builder, extraEnvironment) - local env = {} - - if extraEnvironment then - if type(extraEnvironment) ~= "table" then - error(("Bad argument #2 to TestEnvironment.new. Expected table, got %s"):format( - typeof(extraEnvironment)), 2) - end - - for key, value in pairs(extraEnvironment) do - env[key] = value - end - end - - 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.it(phrase, callback) - local node = builder:pushNode(phrase, TestEnum.NodeType.It) - - node.callback = callback - - builder:popNode() - end - - -- Incrementing counter used to ensure that beforeAll, afterAll, beforeEach, afterEach have unique phrases - local lifecyclePhaseId = 0 - - local lifecycleHooks = { - [TestEnum.NodeType.BeforeAll] = "beforeAll", - [TestEnum.NodeType.AfterAll] = "afterAll", - [TestEnum.NodeType.BeforeEach] = "beforeEach", - [TestEnum.NodeType.AfterEach] = "afterEach" - } - - for nodeType, name in pairs(lifecycleHooks) do - env[name] = function(callback) - local node = builder:pushNode(name .. "_" .. tostring(lifecyclePhaseId), nodeType) - lifecyclePhaseId = lifecyclePhaseId + 1 - - node.callback = callback - - builder:popNode() - end - 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() - warn("HACK_NO_XPCALL is deprecated. It is now safe to yield in an " .. - "xpcall, so this is no longer necessary. It can be safely deleted.") - end - - env.fit = env.itFOCUS - env.xit = env.itSKIP - env.fdescribe = env.describeFOCUS - env.xdescribe = env.describeSKIP - - setmetatable(env, TestEnvironment) - return env -end - -return TestEnvironment \ No newline at end of file diff --git a/src/TestPlan.lua b/src/TestPlan.lua index ce6759e..3a4de71 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -7,6 +7,111 @@ local TestEnum = require(script.Parent.TestEnum) +local function newEnvironment(currentNode, extraEnvironment) + local env = {} + + if extraEnvironment then + if type(extraEnvironment) ~= "table" then + error(("Bad argument #2 to newEnvironment. Expected table, got %s"):format( + typeof(extraEnvironment)), 2) + end + + for key, value in pairs(extraEnvironment) do + env[key] = value + end + end + + 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 = currentNode:addChild(phrase, TestEnum.NodeType.Describe, nodeModifier) + node.callback = callback + node:expand() + return node + end + + function env.it(phrase, callback, nodeModifier) + local node = currentNode:addChild(phrase, TestEnum.NodeType.It, nodeModifier) + node.callback = callback + return node + end + + -- Incrementing counter used to ensure that beforeAll, afterAll, beforeEach, afterEach have unique phrases + local lifecyclePhaseId = 0 + + local lifecycleHooks = { + [TestEnum.NodeType.BeforeAll] = "beforeAll", + [TestEnum.NodeType.AfterAll] = "afterAll", + [TestEnum.NodeType.BeforeEach] = "beforeEach", + [TestEnum.NodeType.AfterEach] = "afterEach" + } + + for nodeType, name in pairs(lifecycleHooks) do + env[name] = function(callback) + local node = currentNode:addChild(name .. "_" .. tostring(lifecyclePhaseId), nodeType) + lifecyclePhaseId = lifecyclePhaseId + 1 + + node.callback = callback + return node + end + end + + function env.itFOCUS(phrase, callback) + return env.it(phrase, callback, TestEnum.NodeModifier.Focus) + end + + function env.itSKIP(phrase, callback) + return env.it(phrase, callback, TestEnum.NodeModifier.Skip) + end + + function env.itFIXME(phrase, callback) + local node = env.it(phrase, callback, TestEnum.NodeModifier.Skip) + warn("FIXME: broken test", node:getFullName()) + return node + end + + function env.FIXME(optionalMessage) + warn("FIXME: broken test", currentNode:getFullName(), optionalMessage or "") + + currentNode.modifier = TestEnum.NodeModifier.Skip + end + + function env.FOCUS() + currentNode.modifier = TestEnum.NodeModifier.Focus + end + + function env.SKIP() + 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() + warn("HACK_NO_XPCALL is deprecated. It is now safe to yield in an " .. + "xpcall, so this is no longer necessary. It can be safely deleted.") + end + + env.fit = env.itFOCUS + env.xit = env.itSKIP + env.fdescribe = env.describeFOCUS + env.xdescribe = env.describeSKIP + + return env +end + local TestPlan = {} TestPlan.__index = TestPlan @@ -14,56 +119,129 @@ TestPlan.__index = TestPlan --[[ Create a new, empty TestPlan. ]] -function TestPlan.new() - local self = { - children = {} +function TestPlan.new(testNamePattern, extraEnvironment) + local plan = { + children = {}, + testNamePattern = testNamePattern, } - setmetatable(self, TestPlan) + local Node = {} + Node.__index = Node - return self -end + function Node.new(phrase, nodeType, nodeModifier) + nodeModifier = nodeModifier or TestEnum.NodeModifier.None ---[[ - Calls the given callback on all nodes in the tree, traversed depth-first. -]] -function TestPlan:visitAllNodes(callback, root, level) - root = root or self - level = level or 0 + local node = { + phrase = phrase, + type = nodeType, + modifier = nodeModifier, + children = {}, + callback = nil, + } - for _, child in ipairs(root.children) do - callback(child, level) + node.environment = newEnvironment(node, extraEnvironment) + return setmetatable(node, Node) + end - self:visitAllNodes(callback, child, level + 1) + function Node:addChild(phrase, nodeType, nodeModifier) + if testNamePattern and (nodeModifier == nil or nodeModifier == TestEnum.NodeModifier.None) then + local name = self:getFullName() .. " " .. phrase + if name:match(testNamePattern) then + nodeModifier = TestEnum.NodeModifier.Focus + else + nodeModifier = TestEnum.NodeModifier.Skip + end + end + local child = Node.new(phrase, nodeType, nodeModifier) + child.parent = self + table.insert(self.children, child) + return child + end + + function Node:getFullName() + if self.parent and self.parent.getFullName then + local parentPhrase = self.parent:getFullName() + if parentPhrase then + return parentPhrase .. " " .. self.phrase + end + end + return self.phrase end + + function Node:expand() + local originalEnv = getfenv(self.callback) + local callbackEnv = setmetatable({}, { __index = originalEnv }) + for key, value in pairs(self.environment) do + callbackEnv[key] = value + end + setfenv(self.callback, callbackEnv) + + local success, result = xpcall(self.callback, function(err) + return err .. "\n" .. debug.traceback() + end) + + if not success then + self.loadError = result + end + end + + plan.Node = Node + + return setmetatable(plan, TestPlan) end -local function constructNodeFullName(node) - if node.parent then - local parentPhrase = constructNodeFullName(node.parent) - if parentPhrase then - return parentPhrase .. " " .. node.phrase +function TestPlan:addChild(phrase, nodeType, nodeModifier) + if self.testNamePattern and (nodeModifier == nil or nodeModifier == TestEnum.NodeModifier.None) then + if phrase:match(self.testNamePattern) then + nodeModifier = TestEnum.NodeModifier.Focus + else + nodeModifier = TestEnum.NodeModifier.Skip end end - return node.phrase + local child = self.Node.new(phrase, nodeType, nodeModifier) + child.parent = self + table.insert(self.children, child) + return child end --[[ - Creates a new node that would be suitable to insert into the TestPlan. + ]] -function TestPlan.createNode(phrase, nodeType, nodeModifier) - nodeModifier = nodeModifier or TestEnum.NodeModifier.None +function TestPlan:addRoot(path, method) + local curNode = self + for i = #path, 1, -1 do + local nextNode = nil - local node = { - phrase = phrase, - type = nodeType, - modifier = nodeModifier, - children = {}, - callback = nil, - getFullName = constructNodeFullName - } + for _, child in ipairs(curNode.children) do + if child.phrase == path[i] then + nextNode = child + break + end + end - return node + if nextNode == nil then + nextNode = curNode:addChild(path[i], TestEnum.NodeType.Describe) + end + + curNode = nextNode + end + + curNode.callback = method + curNode:expand() +end + +--[[ + Calls the given callback on all nodes in the tree, traversed depth-first. +]] +function TestPlan:visitAllNodes(callback, root, level) + root = root or self + level = level or 0 + + for _, child in ipairs(root.children) do + callback(child, level) + + self:visitAllNodes(callback, child, level + 1) + end end --[[ diff --git a/src/TestPlanBuilder.lua b/src/TestPlanBuilder.lua deleted file mode 100644 index 841874c..0000000 --- a/src/TestPlanBuilder.lua +++ /dev/null @@ -1,96 +0,0 @@ ---[[ - Represents the ephermal state used for building a TestPlan from some other - representation. - - TestPlanBuilder keeps track of a stack of nodes that represents the current - position in the hierarchy, allowing the consumer to move up and down the - tree as new nodes are discovered. -]] - -local TestPlan = require(script.Parent.TestPlan) -local TestEnum = require(script.Parent.TestEnum) - -local TestPlanBuilder = {} - -TestPlanBuilder.__index = TestPlanBuilder - ---[[ - Create a new TestPlanBuilder, used for creating a TestPlan. -]] -function TestPlanBuilder.new() - local self = { - plan = TestPlan.new(), - nodeStack = {}, - testNamePattern = nil, - } - - setmetatable(self, TestPlanBuilder) - - return self -end - ---[[ - Verify that the TestPlanBuilder's state is valid and get a TestPlan from it. -]] -function TestPlanBuilder:finalize() - if #self.nodeStack ~= 0 then - error("Cannot finalize a TestPlan with nodes still on the stack!", 2) - end - - return self.plan -end - ---[[ - Grab the current node being worked on by the TestPlanBuilder. -]] -function TestPlanBuilder:getCurrentNode() - return self.nodeStack[#self.nodeStack] or self.plan -end - ---[[ - Creates and pushes a node onto the navigation stack. -]] -function TestPlanBuilder:pushNode(phrase, nodeType, nodeModifier) - local lastNode = self.nodeStack[#self.nodeStack] or self.plan - - -- Find an existing node with this phrase to use - local useNode - for _, child in ipairs(lastNode.children) do - if child.phrase == phrase then - useNode = child - break - end - end - - -- Didn't find one, create a new node - if not useNode then - useNode = TestPlan.createNode(phrase, nodeType, nodeModifier) - useNode.parent = lastNode - - table.insert(lastNode.children, useNode) - end - - table.insert(self.nodeStack, useNode) - - local nodeModifierNotSet = useNode.modifier == nil or useNode.modifier == TestEnum.NodeModifier.None - if self.testNamePattern and nodeModifierNotSet then - local fullName = useNode:getFullName() - if fullName:match(self.testNamePattern) then - useNode.modifier = TestEnum.NodeModifier.Focus - else - useNode.modifier = TestEnum.NodeModifier.Skip - end - end - - return useNode -end - ---[[ - Pops a node off of the node navigation stack. -]] -function TestPlanBuilder:popNode() - assert(#self.nodeStack > 0, "Tried to pop from an empty node stack!") - return table.remove(self.nodeStack, #self.nodeStack) -end - -return TestPlanBuilder \ No newline at end of file diff --git a/src/TestPlanner.lua b/src/TestPlanner.lua index da39b5d..4b9772a 100644 --- a/src/TestPlanner.lua +++ b/src/TestPlanner.lua @@ -3,45 +3,10 @@ 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 TestEnvironment = require(script.Parent.TestEnvironment) +local TestPlan = require(script.Parent.TestPlan) 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 TestPlan from a list of specification functions. @@ -49,15 +14,13 @@ end variants), which will be turned into a test plan to be executed. ]] function TestPlanner.createPlan(specFunctions, testNamePattern, extraEnvironment) - local builder = TestPlanBuilder.new() - builder.testNamePattern = testNamePattern - local env = TestEnvironment.new(builder, extraEnvironment) + local plan = TestPlan.new(testNamePattern, extraEnvironment) for _, module in ipairs(specFunctions) do - buildPlan(builder, module, env) + plan:addRoot(module.path, module.method) end - return builder:finalize() + return plan end return TestPlanner \ No newline at end of file diff --git a/src/init.lua b/src/init.lua index ac3ce3d..9c702a1 100644 --- a/src/init.lua +++ b/src/init.lua @@ -2,7 +2,6 @@ local Expectation = require(script.Expectation) local TestBootstrap = require(script.TestBootstrap) local TestEnum = require(script.TestEnum) local TestPlan = require(script.TestPlan) -local TestPlanBuilder = require(script.TestPlanBuilder) local TestPlanner = require(script.TestPlanner) local TestResults = require(script.TestResults) local TestRunner = require(script.TestRunner) @@ -26,7 +25,6 @@ local TestEZ = { TestBootstrap = TestBootstrap, TestEnum = TestEnum, TestPlan = TestPlan, - TestPlanBuilder = TestPlanBuilder, TestPlanner = TestPlanner, TestResults = TestResults, TestRunner = TestRunner, diff --git a/tests/lifecycleHooks.lua b/tests/lifecycleHooks.lua index 10acd63..1d5225c 100644 --- a/tests/lifecycleHooks.lua +++ b/tests/lifecycleHooks.lua @@ -39,6 +39,10 @@ local function runTestPlan(testPlan) local plan = TestEZ.TestPlanner.createPlan({ { method = function() + -- This function environment hack is needed because the testPlan + -- function is not defined or required from within a test. This + -- shouldn't come up in real tests. + setfenv(testPlan, getfenv()) testPlan(insertLifecycleEvent) end, path = {'lifecycleHooksTest'} From b7e1877c0e6e5a8a833e8d9c2191de1f926043d9 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Fri, 8 May 2020 18:04:59 -0700 Subject: [PATCH 02/20] Fix test name. Now it works as expected the first time --- .../duplicateDescribe.spec.lua} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/{passing/duplicateDescribe.lua => failing/duplicateDescribe.spec.lua} (61%) diff --git a/tests/passing/duplicateDescribe.lua b/tests/failing/duplicateDescribe.spec.lua similarity index 61% rename from tests/passing/duplicateDescribe.lua rename to tests/failing/duplicateDescribe.spec.lua index 8aae411..31a440b 100644 --- a/tests/passing/duplicateDescribe.lua +++ b/tests/failing/duplicateDescribe.spec.lua @@ -2,13 +2,13 @@ return function() describe("with the same description", function() - it("should not run this", function() - error("this won't happen") + it("should run this", function() + error("this won't get overwritten") end) end) describe("with the same description", function() - it("should only run this test", function() + it("should also run this", function() end) end) end \ No newline at end of file From ab4a9acfb7f8b1bcfeb0cb9a2d2d29430be13e3f Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Fri, 8 May 2020 18:09:49 -0700 Subject: [PATCH 03/20] Add a more explicit check of duplicate nodes --- tests/planner.lua | 10 ++++++++++ tests/planning/d.spec.lua | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/planning/d.spec.lua diff --git a/tests/planner.lua b/tests/planner.lua index fb0bfdf..60ca32b 100644 --- a/tests/planner.lua +++ b/tests/planner.lua @@ -62,6 +62,11 @@ return { "planning b test1", "planning b test2", "planning b test2 test3", + "planning d", + "planning d test4", + "planning d test4 test5", + "planning d test4 test6", + "planning d test4 test7", })) end, ["it should mark skipped tests as skipped"] = function() @@ -74,6 +79,11 @@ return { "planning b", "planning b test1", "planning b test2 test3", -- This isn't marked skip, its parent is + "planning d", + "planning d test4", + "planning d test4 test5", + "planning d test4 test6", + "planning d test4 test7", }, true)) end, ["it should skip tests that don't match the filter"] = function() diff --git a/tests/planning/d.spec.lua b/tests/planning/d.spec.lua new file mode 100644 index 0000000..0f88430 --- /dev/null +++ b/tests/planning/d.spec.lua @@ -0,0 +1,21 @@ +-- luacheck: globals describe it SKIP + +return function() + describe("test4", function() + it("test5", function() + end) + + it("test6", function() + end) + end) + + describe("test4", function() + -- Duplicate describe blocks should get merged. + it("test5", function() + -- Duplicate it blocks will get overwritten. + end) + + it("test7", function() + end) + end) +end From ff58302d7bab5e1b29457f6e396983736c3af05f Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Fri, 8 May 2020 18:17:40 -0700 Subject: [PATCH 04/20] Update tests of duplicate it blocks to match new code --- tests/failing/duplicateIt.spec.lua | 13 +++++++++++++ tests/passing/duplicateIt.spec.lua | 18 ------------------ 2 files changed, 13 insertions(+), 18 deletions(-) create mode 100644 tests/failing/duplicateIt.spec.lua delete mode 100644 tests/passing/duplicateIt.spec.lua diff --git a/tests/failing/duplicateIt.spec.lua b/tests/failing/duplicateIt.spec.lua new file mode 100644 index 0000000..6960077 --- /dev/null +++ b/tests/failing/duplicateIt.spec.lua @@ -0,0 +1,13 @@ +-- luacheck: globals describe it + +return function() + describe("multiple it blocks with the same description", function() + it("all get run", function() + end) + it("all get run", function() + error("this shouldn't get overwritten") + end) + it("all get run", function() + end) + end) +end \ No newline at end of file diff --git a/tests/passing/duplicateIt.spec.lua b/tests/passing/duplicateIt.spec.lua deleted file mode 100644 index 1d55c75..0000000 --- a/tests/passing/duplicateIt.spec.lua +++ /dev/null @@ -1,18 +0,0 @@ --- luacheck: globals describe it - -return function() - describe("multiple it blocks with the same description", function() - it("only the last runs", function() - error("first") - end) - it("only the last runs", function() - error("second") - end) - it("only the last runs", function() - error("third") - end) - it("only the last runs", function() - -- This is the only one that will run - end) - end) -end \ No newline at end of file From 79c68b770396fbb8011995c030be5e4d9bf5b558 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Wed, 13 May 2020 13:01:20 -0700 Subject: [PATCH 05/20] Add expectation to environment --- src/TestPlan.lua | 3 +++ tests/passing/expect.spec.lua | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 tests/passing/expect.spec.lua diff --git a/src/TestPlan.lua b/src/TestPlan.lua index 3a4de71..beb5664 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -6,6 +6,7 @@ ]] local TestEnum = require(script.Parent.TestEnum) +local Expectation = require(script.Parent.Expectation) local function newEnvironment(currentNode, extraEnvironment) local env = {} @@ -109,6 +110,8 @@ local function newEnvironment(currentNode, extraEnvironment) env.fdescribe = env.describeFOCUS env.xdescribe = env.describeSKIP + env.expect = Expectation.new + return env end diff --git a/tests/passing/expect.spec.lua b/tests/passing/expect.spec.lua new file mode 100644 index 0000000..7929352 --- /dev/null +++ b/tests/passing/expect.spec.lua @@ -0,0 +1,11 @@ +return function() + local function helper() + expect(1).to.never.equal(2) + end + + describe("from within a describe block", function() + it("helpers should be able to access expect", function() + helper() + end) + end) +end \ No newline at end of file From ed47b2c24d04c3bb9a79584283ec8920be43faed Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Wed, 13 May 2020 13:04:40 -0700 Subject: [PATCH 06/20] Add luacheck globals for new test --- tests/passing/expect.spec.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/passing/expect.spec.lua b/tests/passing/expect.spec.lua index 7929352..726b267 100644 --- a/tests/passing/expect.spec.lua +++ b/tests/passing/expect.spec.lua @@ -1,3 +1,4 @@ +-- luacheck: globals describe it expect return function() local function helper() expect(1).to.never.equal(2) From 04afd682b79fc450510f205406b24d51ba42c361 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Thu, 14 May 2020 13:47:42 -0700 Subject: [PATCH 07/20] Refactor TestNode to keep a pointer back to plan tree --- src/TestPlan.lua | 114 +++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/TestPlan.lua b/src/TestPlan.lua index beb5664..8cd2086 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -115,80 +115,80 @@ local function newEnvironment(currentNode, extraEnvironment) return env end +local TestNode = {} +TestNode.__index = TestNode + local TestPlan = {} TestPlan.__index = TestPlan ---[[ - Create a new, empty TestPlan. -]] -function TestPlan.new(testNamePattern, extraEnvironment) - local plan = { +function TestNode.new(plan, phrase, nodeType, nodeModifier) + nodeModifier = nodeModifier or TestEnum.NodeModifier.None + + local node = { + plan = plan, + phrase = phrase, + type = nodeType, + modifier = nodeModifier, children = {}, - testNamePattern = testNamePattern, + callback = nil, } - local Node = {} - Node.__index = Node - - function Node.new(phrase, nodeType, nodeModifier) - nodeModifier = nodeModifier or TestEnum.NodeModifier.None - - local node = { - phrase = phrase, - type = nodeType, - modifier = nodeModifier, - children = {}, - callback = nil, - } - - node.environment = newEnvironment(node, extraEnvironment) - return setmetatable(node, Node) - end + node.environment = newEnvironment(node, plan.extraEnvironment) + return setmetatable(node, TestNode) +end - function Node:addChild(phrase, nodeType, nodeModifier) - if testNamePattern and (nodeModifier == nil or nodeModifier == TestEnum.NodeModifier.None) then - local name = self:getFullName() .. " " .. phrase - if name:match(testNamePattern) then - nodeModifier = TestEnum.NodeModifier.Focus - else - nodeModifier = TestEnum.NodeModifier.Skip - end +function TestNode:addChild(phrase, nodeType, nodeModifier) + if self.plan.testNamePattern and (nodeModifier == nil or nodeModifier == TestEnum.NodeModifier.None) then + local name = self:getFullName() .. " " .. phrase + if name:match(self.plan.testNamePattern) then + nodeModifier = TestEnum.NodeModifier.Focus + else + nodeModifier = TestEnum.NodeModifier.Skip end - local child = Node.new(phrase, nodeType, nodeModifier) - child.parent = self - table.insert(self.children, child) - return child end + local child = TestNode.new(self.plan, phrase, nodeType, nodeModifier) + child.parent = self + table.insert(self.children, child) + return child +end - function Node:getFullName() - if self.parent and self.parent.getFullName then - local parentPhrase = self.parent:getFullName() - if parentPhrase then - return parentPhrase .. " " .. self.phrase - end +function TestNode:getFullName() + if self.parent and self.parent.getFullName then + local parentPhrase = self.parent:getFullName() + if parentPhrase then + return parentPhrase .. " " .. self.phrase end - return self.phrase end + return self.phrase +end - function Node:expand() - local originalEnv = getfenv(self.callback) - local callbackEnv = setmetatable({}, { __index = originalEnv }) - for key, value in pairs(self.environment) do - callbackEnv[key] = value - end - setfenv(self.callback, callbackEnv) +function TestNode:expand() + local originalEnv = getfenv(self.callback) + local callbackEnv = setmetatable({}, { __index = originalEnv }) + for key, value in pairs(self.environment) do + callbackEnv[key] = value + end + setfenv(self.callback, callbackEnv) - local success, result = xpcall(self.callback, function(err) - return err .. "\n" .. debug.traceback() - end) + local success, result = xpcall(self.callback, function(err) + return err .. "\n" .. debug.traceback() + end) - if not success then - self.loadError = result - end + if not success then + self.loadError = result end +end - plan.Node = Node +--[[ + Create a new, empty TestPlan. +]] +function TestPlan.new(testNamePattern, extraEnvironment) + local plan = { + children = {}, + testNamePattern = testNamePattern, + extraEnvironment = extraEnvironment, + } return setmetatable(plan, TestPlan) end @@ -201,7 +201,7 @@ function TestPlan:addChild(phrase, nodeType, nodeModifier) nodeModifier = TestEnum.NodeModifier.Skip end end - local child = self.Node.new(phrase, nodeType, nodeModifier) + local child = TestNode.new(self, phrase, nodeType, nodeModifier) child.parent = self table.insert(self.children, child) return child From a33e15869520f271ce7d74242cfaef39ff873e8f Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Thu, 14 May 2020 13:55:50 -0700 Subject: [PATCH 08/20] Update comments --- src/TestPlan.lua | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/TestPlan.lua b/src/TestPlan.lua index 8cd2086..4c3ec30 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -92,13 +92,8 @@ local function newEnvironment(currentNode, extraEnvironment) 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. + This function is deprecated. Calling it is a no-op beyond generating a + warning. ]] function env.HACK_NO_XPCALL() warn("HACK_NO_XPCALL is deprecated. It is now safe to yield in an " .. @@ -118,10 +113,11 @@ end local TestNode = {} TestNode.__index = TestNode -local TestPlan = {} - -TestPlan.__index = TestPlan - +--[[ + Create a new test node. A pointer to the test plan, a phrase to describe it + and the type of node it is are required. The modifier is option and will be + None if left blank. +]] function TestNode.new(plan, phrase, nodeType, nodeModifier) nodeModifier = nodeModifier or TestEnum.NodeModifier.None @@ -153,6 +149,9 @@ function TestNode:addChild(phrase, nodeType, nodeModifier) return child end +--[[ + Join the names of all the nodes back to the parent. +]] function TestNode:getFullName() if self.parent and self.parent.getFullName then local parentPhrase = self.parent:getFullName() @@ -163,6 +162,10 @@ function TestNode:getFullName() return self.phrase end +--[[ + Expand a node by setting its callback environment and then calling it. Any + further it and describe calls within the callback will be added to the tree. +]] function TestNode:expand() local originalEnv = getfenv(self.callback) local callbackEnv = setmetatable({}, { __index = originalEnv }) @@ -180,6 +183,9 @@ function TestNode:expand() end end +local TestPlan = {} +TestPlan.__index = TestPlan + --[[ Create a new, empty TestPlan. ]] @@ -193,6 +199,9 @@ function TestPlan.new(testNamePattern, extraEnvironment) return setmetatable(plan, TestPlan) end +--[[ + Add a new child under the test plan's root node. +]] function TestPlan:addChild(phrase, nodeType, nodeModifier) if self.testNamePattern and (nodeModifier == nil or nodeModifier == TestEnum.NodeModifier.None) then if phrase:match(self.testNamePattern) then @@ -208,7 +217,8 @@ function TestPlan:addChild(phrase, nodeType, nodeModifier) end --[[ - + Add a new describe node with the given method as a callback. Generates or + reuses all the describe nodes along the path. ]] function TestPlan:addRoot(path, method) local curNode = self From 90d935a8cf33154a37953b5db81a3d88a865c7ad Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Thu, 14 May 2020 14:04:42 -0700 Subject: [PATCH 09/20] Typo --- src/TestPlan.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TestPlan.lua b/src/TestPlan.lua index 4c3ec30..c9086d7 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -115,8 +115,8 @@ TestNode.__index = TestNode --[[ Create a new test node. A pointer to the test plan, a phrase to describe it - and the type of node it is are required. The modifier is option and will be - None if left blank. + and the type of node it is are required. The modifier is optional and will + be None if left blank. ]] function TestNode.new(plan, phrase, nodeType, nodeModifier) nodeModifier = nodeModifier or TestEnum.NodeModifier.None From 5ed50deafe60febf3393186ade172d400a1e848e Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Thu, 14 May 2020 14:15:45 -0700 Subject: [PATCH 10/20] Remove odd plan-as-parent pointer since the plan is stored explicitly --- src/TestPlan.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TestPlan.lua b/src/TestPlan.lua index c9086d7..b61a479 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -128,6 +128,7 @@ function TestNode.new(plan, phrase, nodeType, nodeModifier) modifier = nodeModifier, children = {}, callback = nil, + parent = nil, } node.environment = newEnvironment(node, plan.extraEnvironment) @@ -153,7 +154,7 @@ end Join the names of all the nodes back to the parent. ]] function TestNode:getFullName() - if self.parent and self.parent.getFullName then + if self.parent then local parentPhrase = self.parent:getFullName() if parentPhrase then return parentPhrase .. " " .. self.phrase @@ -211,7 +212,6 @@ function TestPlan:addChild(phrase, nodeType, nodeModifier) end end local child = TestNode.new(self, phrase, nodeType, nodeModifier) - child.parent = self table.insert(self.children, child) return child end From 0a37fb85fc90cf3ceb5979f317ea961a4e0aa614 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Thu, 14 May 2020 14:24:36 -0700 Subject: [PATCH 11/20] Remove reference to TestPlanBuilder --- src/TestPlan.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestPlan.lua b/src/TestPlan.lua index b61a479..9f36ede 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -2,7 +2,7 @@ Represents a tree of tests that have been loaded but not necessarily executed yet. - TestPlan objects are produced by TestPlanner and TestPlanBuilder. + TestPlan objects are produced by TestPlanner. ]] local TestEnum = require(script.Parent.TestEnum) From e3249cb286d1e95ce4ea226694fe2b9d373d4fd0 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Mon, 18 May 2020 18:10:56 -0700 Subject: [PATCH 12/20] Tests for init.spec ordering --- tests/expandOrder.lua | 25 +++++++++++++++++++++++++ tests/planning/init.spec.lua | 4 ++++ 2 files changed, 29 insertions(+) create mode 100644 tests/expandOrder.lua create mode 100644 tests/planning/init.spec.lua diff --git a/tests/expandOrder.lua b/tests/expandOrder.lua new file mode 100644 index 0000000..6e70ae0 --- /dev/null +++ b/tests/expandOrder.lua @@ -0,0 +1,25 @@ +local TestEZ = require(script.Parent.Parent.TestEZ) + +return { + ["init.spec.lua is run before children are expanded"] = function() + local initialized = false + + local plan = TestEZ.TestPlanner.createPlan({ + { + method = function() + assert(initialized, "init.spec was not called before bar.spec") + end, + path = {'bar.spec', 'foo'} + }, + { + method = function() + initialized = true + end, + path = {'foo'} + }, + }) + + local results = TestEZ.TestRunner.runPlan(plan) + assert(#results.errors == 0, "init test failed: " .. tostring(results.errors[1])) + end, +} \ No newline at end of file diff --git a/tests/planning/init.spec.lua b/tests/planning/init.spec.lua new file mode 100644 index 0000000..eef2433 --- /dev/null +++ b/tests/planning/init.spec.lua @@ -0,0 +1,4 @@ +-- This should be added to the "planning" node of the tree instead of creating +-- a new node. +return function() +end From 062408ae9e6d60bc6c0082f9771c6dab98a9e806 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Mon, 18 May 2020 18:14:14 -0700 Subject: [PATCH 13/20] Use a finalize function to be sure order is correct --- src/TestPlan.lua | 25 +++++++++++++++++++++---- src/TestPlanner.lua | 1 + 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/TestPlan.lua b/src/TestPlan.lua index 9f36ede..a869a0d 100644 --- a/src/TestPlan.lua +++ b/src/TestPlan.lua @@ -33,7 +33,7 @@ local function newEnvironment(currentNode, extraEnvironment) function env.describe(phrase, callback, nodeModifier) local node = currentNode:addChild(phrase, TestEnum.NodeType.Describe, nodeModifier) node.callback = callback - node:expand() + --node:expand() return node end @@ -135,6 +135,9 @@ function TestNode.new(plan, phrase, nodeType, nodeModifier) return setmetatable(node, TestNode) end +--[[ + Add a child to this node, applying the test name pattern in the process. +]] function TestNode:addChild(phrase, nodeType, nodeModifier) if self.plan.testNamePattern and (nodeModifier == nil or nodeModifier == TestEnum.NodeModifier.None) then local name = self:getFullName() .. " " .. phrase @@ -164,10 +167,14 @@ function TestNode:getFullName() end --[[ - Expand a node by setting its callback environment and then calling it. Any - further it and describe calls within the callback will be added to the tree. + Expand a node by setting its callback environment and then calling it. Only + expands this one node. ]] function TestNode:expand() + if not self.callback then + return + end + local originalEnv = getfenv(self.callback) local callbackEnv = setmetatable({}, { __index = originalEnv }) for key, value in pairs(self.environment) do @@ -240,7 +247,17 @@ function TestPlan:addRoot(path, method) end curNode.callback = method - curNode:expand() +end + +--[[ + Expands all describe nodes, leaving the plan in a runnable state. +]] +function TestPlan:finalize() + self:visitAllNodes(function(node) + if node.type == TestEnum.NodeType.Describe then + node:expand() + end + end) end --[[ diff --git a/src/TestPlanner.lua b/src/TestPlanner.lua index 4b9772a..484157d 100644 --- a/src/TestPlanner.lua +++ b/src/TestPlanner.lua @@ -20,6 +20,7 @@ function TestPlanner.createPlan(specFunctions, testNamePattern, extraEnvironment plan:addRoot(module.path, module.method) end + plan:finalize() return plan end From 5ae438d499b4df09c4ab251729dbcfe6f309ae05 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Mon, 18 May 2020 18:23:11 -0700 Subject: [PATCH 14/20] Another test to make sure init.spec and afterAll work together --- tests/expandOrder.lua | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/expandOrder.lua b/tests/expandOrder.lua index 6e70ae0..6d582a4 100644 --- a/tests/expandOrder.lua +++ b/tests/expandOrder.lua @@ -22,4 +22,50 @@ return { local results = TestEZ.TestRunner.runPlan(plan) assert(#results.errors == 0, "init test failed: " .. tostring(results.errors[1])) end, + ["init.spec.lua afterAll can correctly undo changes"] = function() + local initialized = false + + local plan = TestEZ.TestPlanner.createPlan({ + { + method = function() + assert(not initialized, "initialized was true in foo/a.spec") + end, + path = {'a.spec', 'foo'} + }, + { + method = function() + assert(initialized, "initialized was false in foo/bar/b.spec") + end, + path = {'b.spec', 'bar', 'foo'} + }, + { + method = function() + initialized = true + + -- luacheck: globals afterAll + afterAll(function() + print("!!!!!!!!!!!!!") + initialized = false + end) + end, + path = {'bar', 'foo'} + }, + { + method = function() + assert(initialized, "initialized was false in foo/bar/c.spec") + end, + path = {'c.spec', 'bar', 'foo'} + }, + { + method = function() + assert(not initialized, "initialized was true in foo/d.spec") + end, + path = {'d.spec', 'foo'} + }, + }) + + local results = TestEZ.TestRunner.runPlan(plan) + assert(#results.errors == 0, "init test failed:\n" .. + table.concat(results.errors, "\n")) + end, } \ No newline at end of file From e90c1df424a4d0995489e6fc9005dee0d184a5df Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Mon, 18 May 2020 18:23:53 -0700 Subject: [PATCH 15/20] Remove debug print --- tests/expandOrder.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/expandOrder.lua b/tests/expandOrder.lua index 6d582a4..0aea59b 100644 --- a/tests/expandOrder.lua +++ b/tests/expandOrder.lua @@ -44,7 +44,6 @@ return { -- luacheck: globals afterAll afterAll(function() - print("!!!!!!!!!!!!!") initialized = false end) end, From 4b5a677bb74d1c4529ab17f7a324a34b977fd1fd Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Tue, 19 May 2020 11:49:24 -0700 Subject: [PATCH 16/20] Expand test of lifecycle hooks --- tests/lifecycleHooks.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/lifecycleHooks.lua b/tests/lifecycleHooks.lua index 1d5225c..ee387f6 100644 --- a/tests/lifecycleHooks.lua +++ b/tests/lifecycleHooks.lua @@ -137,6 +137,10 @@ return { end) end) end) + + it("runs root again", function() + insertLifecycleEvent("1 - another test") + end) end) expectShallowEquals(lifecycleOrder, { @@ -151,6 +155,9 @@ return { "2 - afterEach", "1 - afterEach", "2 - afterAll", + "1 - beforeEach", + "1 - another test", + "1 - afterEach", "1 - afterAll", }) expectNoFailures(results) From 5265c39b96ec698e7345149240be2e757d0ec8fb Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Tue, 19 May 2020 11:49:24 -0700 Subject: [PATCH 17/20] Expand test of lifecycle hooks --- tests/lifecycleHooks.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/lifecycleHooks.lua b/tests/lifecycleHooks.lua index 10acd63..02e76c7 100644 --- a/tests/lifecycleHooks.lua +++ b/tests/lifecycleHooks.lua @@ -133,6 +133,10 @@ return { end) end) end) + + it("runs root again", function() + insertLifecycleEvent("1 - another test") + end) end) expectShallowEquals(lifecycleOrder, { @@ -147,6 +151,9 @@ return { "2 - afterEach", "1 - afterEach", "2 - afterAll", + "1 - beforeEach", + "1 - another test", + "1 - afterEach", "1 - afterAll", }) expectNoFailures(results) From 815f0eeaa7a2512c4468f9e4942a42971b33fc11 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Tue, 19 May 2020 14:04:07 -0700 Subject: [PATCH 18/20] Fix #101, change beforeAll and afterAll hook implementation --- src/LifecycleHooks.lua | 105 +++++++++------------------------------ tests/lifecycleHooks.lua | 7 +++ 2 files changed, 30 insertions(+), 82 deletions(-) diff --git a/src/LifecycleHooks.lua b/src/LifecycleHooks.lua index 3ef05fe..288a680 100644 --- a/src/LifecycleHooks.lua +++ b/src/LifecycleHooks.lua @@ -7,8 +7,7 @@ function LifecycleHooks.new() local self = { _stack = {}, } - setmetatable(self, LifecycleHooks) - return self + return setmetatable(self, LifecycleHooks) end --[[ @@ -33,6 +32,7 @@ end function LifecycleHooks:getAfterEachHooks() local key = TestEnum.NodeType.AfterEach local hooks = {} + for _, level in ipairs(self._stack) do for _, hook in ipairs(level[key]) do table.insert(hooks, 1, hook) @@ -46,119 +46,60 @@ end Pushes uncalled beforeAll and afterAll hooks back up the stack ]] function LifecycleHooks:popHooks() - local popped = self._stack[#self._stack] table.remove(self._stack, #self._stack) - - local function pushHooksUp(type) - - local back = self:_getBackOfStack() - - if not back then - return - end - - back[type] = popped[type] - end - - pushHooksUp(TestEnum.NodeType.BeforeAll) - pushHooksUp(TestEnum.NodeType.AfterAll) end function LifecycleHooks:pushHooksFrom(planNode) assert(planNode ~= nil) table.insert(self._stack, { - [TestEnum.NodeType.BeforeAll] = self:_getBeforeAllHooksUncalledAtCurrentLevel(planNode.children), - [TestEnum.NodeType.AfterAll] = self:_getAfterAllHooksUncalledAtCurrentLevel(planNode.children), + [TestEnum.NodeType.BeforeAll] = self:_getHooksOfType(planNode.children, TestEnum.NodeType.BeforeAll), + [TestEnum.NodeType.AfterAll] = self:_getHooksOfType(planNode.children, TestEnum.NodeType.AfterAll), [TestEnum.NodeType.BeforeEach] = self:_getHooksOfType(planNode.children, TestEnum.NodeType.BeforeEach), [TestEnum.NodeType.AfterEach] = self:_getHooksOfType(planNode.children, TestEnum.NodeType.AfterEach), }) end -function LifecycleHooks:getPendingBeforeAllHooks() - return self:_getAndClearPendingHooks(TestEnum.NodeType.BeforeAll) -end - -function LifecycleHooks:getAfterAllHooks() - if #self._stack > 0 then - return self:_getAndClearPendingHooks(TestEnum.NodeType.AfterAll) - else - return {} - end -end - --[[ - Return any hooks that have not yet been returned for this key and clear those hooks + Get all currently uncalled beforeAll hooks, and remove them from the stack. ]] -function LifecycleHooks:_getAndClearPendingHooks(key) - assert(key ~= nil) - - if #self._stack > 0 then - - local back = self._stack[#self._stack] - - local hooks = back[key] - - back[key] = {} - - return hooks - else - return {} - end -end - ---[[ - Transfers uncalled beforeAll and afterAll hooks down the stack -]] -function LifecycleHooks:_getBeforeAllHooksUncalledAtCurrentLevel(childNodes) - local hookType = TestEnum.NodeType.BeforeAll - local hooks = self:_getHooksOfTypeFromBackOfStack(hookType) - - for _, hook in pairs(self:_getHooksOfType(childNodes, hookType)) do - table.insert(hooks, hook) - end - - return hooks -end - -function LifecycleHooks:_getAfterAllHooksUncalledAtCurrentLevel(childNodes) - local hookType = TestEnum.NodeType.AfterAll - local hooks = self:_getHooksOfTypeFromBackOfStack(hookType) +function LifecycleHooks:getPendingBeforeAllHooks() + local key = TestEnum.NodeType.BeforeAll + local hooks = {} - for _, hook in pairs(self:_getHooksOfType(childNodes, hookType)) do - table.insert(hooks, 1, hook) + for _, level in ipairs(self._stack) do + for _, hook in ipairs(level[key]) do + table.insert(hooks, hook) + end + level[key] = {} end return hooks end -function LifecycleHooks:_getHooksOfTypeFromBackOfStack(hookType) - assert(hookType, "Expected hookType to be an argument") - - local currentBack = self:_getBackOfStack() - +--[[ + Get all uncalled afterAll hooks from the back of the stack and remove them. +]] +function LifecycleHooks:getAfterAllHooks() + local key = TestEnum.NodeType.AfterAll local hooks = {} + local currentBack = self._stack[#self._stack] or nil if currentBack then - for _, hook in pairs(currentBack[hookType]) do + for _, hook in pairs(currentBack[key]) do table.insert(hooks, hook) end - - currentBack[hookType] = {} + currentBack[key] = {} end return hooks end -function LifecycleHooks:_getBackOfStack() - return self._stack[#self._stack] or nil -end - -function LifecycleHooks:_getHooksOfType(nodes, type) +function LifecycleHooks:_getHooksOfType(nodes, key) local hooks = {} for _, node in pairs(nodes) do - if node.type == type then + if node.type == key then table.insert(hooks, node.callback) end end diff --git a/tests/lifecycleHooks.lua b/tests/lifecycleHooks.lua index 02e76c7..1f1d813 100644 --- a/tests/lifecycleHooks.lua +++ b/tests/lifecycleHooks.lua @@ -71,6 +71,10 @@ return { it("runs root", function() insertLifecycleEvent("1 - test") end) + + it("runs root again", function() + insertLifecycleEvent("1 - another test") + end) end) expectShallowEquals(lifecycleOrder, { @@ -78,6 +82,9 @@ return { "1 - beforeEach", "1 - test", "1 - afterEach", + "1 - beforeEach", + "1 - another test", + "1 - afterEach", "1 - afterAll", }) From 9060717064d6897f0bf22e450f3f37b1ee81c966 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Tue, 19 May 2020 16:47:38 -0700 Subject: [PATCH 19/20] Mention change in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0d936e..f3fc852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Remove the `step` alias for `it` since that's meant for use with `try`. * Remove the `include` global function. * Remove `HACK_NO_XPCALL`. With recent changes to the definition of xpcall, this is no longer necessary. Since people are still using it, it will now print out a warning asking them to delete that call instead. +* Guarantee that `init.spec.lua` will run before any `it` or `describe` blocks in the folder under it. ## 0.2.0 (2020-03-04) * Added support for init.spec.lua. Code in this file is treated as belonging to the directory's node in the test tree. This allows for lifecycle hooks to be attached to all files in a directory. From 5b0d20e1d5c65002ac8d38d7aaa4573530fb6331 Mon Sep 17 00:00:00 2001 From: MagiMaster Date: Wed, 20 May 2020 11:37:50 -0700 Subject: [PATCH 20/20] Add it blocks for expand test --- tests/expandOrder.lua | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/expandOrder.lua b/tests/expandOrder.lua index 0aea59b..2dbf01e 100644 --- a/tests/expandOrder.lua +++ b/tests/expandOrder.lua @@ -25,25 +25,36 @@ return { ["init.spec.lua afterAll can correctly undo changes"] = function() local initialized = false + -- luacheck: globals it local plan = TestEZ.TestPlanner.createPlan({ { method = function() - assert(not initialized, "initialized was true in foo/a.spec") + print("Ad") + it("A", function() + print("Ai") + assert(not initialized, "initialized was true in foo/a.spec") + end) end, path = {'a.spec', 'foo'} }, { method = function() - assert(initialized, "initialized was false in foo/bar/b.spec") + print("Bd") + it("B", function() + print("Bi") + assert(initialized, "initialized was false in foo/bar/b.spec") + end) end, path = {'b.spec', 'bar', 'foo'} }, { method = function() + print("Init") initialized = true -- luacheck: globals afterAll afterAll(function() + print("After") initialized = false end) end, @@ -51,13 +62,21 @@ return { }, { method = function() - assert(initialized, "initialized was false in foo/bar/c.spec") + print("Cd") + it("C", function() + print("Ci") + assert(initialized, "initialized was false in foo/bar/c.spec") + end) end, path = {'c.spec', 'bar', 'foo'} }, { method = function() - assert(not initialized, "initialized was true in foo/d.spec") + print("Dd") + it("D", function() + print("Di") + assert(not initialized, "initialized was true in foo/d.spec") + end) end, path = {'d.spec', 'foo'} },