function TestBootstrap.run(b,s,N,g)N=N or TextReporter g=g or{}local I=g.showTimingInfo or false local D={}local Q=_G.TESTEZ_TEST_NAME_PATTERN local n=_G.TESTEZ_TEST_PATH_PATTERN local o=_G.TESTEZ_TEST_PATH_IGNORE_PATTERNS local V=g.extraEnvironment or{}for s in string.gmatch(_G.TESTEZ_RUN_TESTS_PATH or"","\040\091\094\044\093\043\041")do table.insert(D,s)end if type(s)~="\116\097\098\108\101"then error(("\066\097\100 \097\114\103\117\109\101\110\116 \035\049 \116\111 \084\101\115\116\066\111\111\116\115\116\114\097\112\058\114\117\110\046 \069\120\112\101\099\116\101\100 \116\097\098\108\101\044 \103\111\116 \037\115"):format(typeof(s)),2)end local j=tick()local m={}for s,N in ipairs(s)do local g=b:getModules(N)for s,N in ipairs(g)do table.insert(m,N)end end local W=tick()local l={runTestsByPath=D,testNamePattern=Q;testPathPattern=n;testPathIgnorePatterns=o;extraEnvironment=V}local T=TestPlanner.createPlan(m,l)local p=tick()local B=TestRunner.runPlan(T)local M=tick()N.report(B)local v=tick()if I then local s={("\084\111\111\107 \037\102 \115\101\099\111\110\100\115 \116\111 \108\111\099\097\116\101 \116\101\115\116 \109\111\100\117\108\101\115"):format(W-j);("\084\111\111\107 \037\102 \115\101\099\111\110\100\115 \116\111 \099\114\101\097\116\101 \116\101\115\116 \112\108\097\110"):format(p-W),("\084\111\111\107 \037\102 \115\101\099\111\110\100\115 \116\111 \114\117\110 \116\101\115\116\115"):format(M-p);("\084\111\111\107 \037\102 \115\101\099\111\110\100\115 \116\111 \114\101\112\111\114\116 \116\101\115\116\115"):format(v-M)}print(table.concat(s,"\n"))end return B end return TestBootstrap
--[[
	Runs all test and reports the results using the given test reporter.

	If no reporter is specified, a reasonable default is provided.

	This function demonstrates the expected workflow with this testing system:
	1. Locate test modules
	2. Generate test plan
	3. Run test plan
	4. Report test results

	This means we could hypothetically present a GUI to the developer that shows
	the test plan before we execute it, allowing them to toggle specific tests
	before they're run, but after they've been identified!
]]
function TestBootstrap:run(roots, reporter, otherOptions)
	reporter = reporter or TextReporter

	otherOptions = otherOptions or {}
	local showTimingInfo = otherOptions["showTimingInfo"] or false
	local runTestsByPath = {}
	local testNamePattern = _G["TESTEZ_TEST_NAME_PATTERN"]
	local testPathPattern = _G["TESTEZ_TEST_PATH_PATTERN"]
	local testPathIgnorePatterns = _G["TESTEZ_TEST_PATH_IGNORE_PATTERNS"]
	local extraEnvironment = otherOptions["extraEnvironment"] or {}

	for testPath in string.gmatch(_G["TESTEZ_RUN_TESTS_PATH"] or "", "([^,]+)") do
		table.insert(runTestsByPath, testPath)
	end

	if type(roots) ~= "table" then
		error(("Bad argument #1 to TestBootstrap:run. Expected table, got %s"):format(typeof(roots)), 2)
	end

	local startTime = tick()

	local modules = {}
	for _, subRoot in ipairs(roots) do
		local newModules = self:getModules(subRoot)

		for _, newModule in ipairs(newModules) do
			table.insert(modules, newModule)
		end
	end

	local afterModules = tick()

	local planArgs = {
		runTestsByPath = runTestsByPath,
		testNamePattern = testNamePattern,
		testPathPattern = testPathPattern,
		testPathIgnorePatterns = testPathIgnorePatterns,
		extraEnvironment = extraEnvironment,
	}

	local plan = TestPlanner.createPlan(modules, planArgs)

	local afterPlan = tick()

	local results = TestRunner.runPlan(plan)
	local afterRun = tick()

	reporter.report(results)
	local afterReport = tick()

	if showTimingInfo then
		local timing = {
			("Took %f seconds to locate test modules"):format(afterModules - startTime),
			("Took %f seconds to create test plan"):format(afterPlan - afterModules),
			("Took %f seconds to run tests"):format(afterRun - afterPlan),
			("Took %f seconds to report tests"):format(afterReport - afterRun),
		}

		print(table.concat(timing, "\n"))
	end

	return results
end

return TestBootstrap
