TestSuite.lua 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. TestSuite = {
  2. -- @method - TestSuite:new()
  3. -- @desc - creates a new TestSuite object that handles all the tests
  4. -- @return {table} - returns the new TestSuite object
  5. new = function(self)
  6. local test = {
  7. -- testsuite internals
  8. modules = {},
  9. module = nil,
  10. test = nil,
  11. testcanvas = nil,
  12. current = 1,
  13. output = '',
  14. totals = {0, 0, 0},
  15. time = 0,
  16. xml = '',
  17. html = '',
  18. mdrows = '',
  19. mdfailures = '',
  20. delayed = nil,
  21. fakequit = false,
  22. windowmode = true,
  23. current_os = love._os,
  24. lua_version = tonumber(_VERSION:match("%d%.%d")),
  25. has_lua_jit = type(jit) == 'table',
  26. -- love modules to test
  27. audio = {},
  28. data = {},
  29. event = {},
  30. filesystem = {},
  31. font = {},
  32. graphics = {},
  33. image = {},
  34. joystick = {},
  35. love = {},
  36. keyboard = {},
  37. math = {},
  38. mouse = {},
  39. physics = {},
  40. sensor = {},
  41. sound = {},
  42. system = {},
  43. thread = {},
  44. timer = {},
  45. touch = {},
  46. video = {},
  47. window = {}
  48. }
  49. setmetatable(test, self)
  50. self.__index = self
  51. return test
  52. end,
  53. -- @method - TestSuite:runSuite()
  54. -- @desc - called in love.update, runs through every method or every module
  55. -- @param {number} delta - delta from love.update to track time elapsed
  56. -- @return {nil}
  57. runSuite = function(self, delta)
  58. -- stagger between tests
  59. if self.module ~= nil then
  60. if self.module.start then
  61. -- work through each test method 1 by 1
  62. if self.module.index <= #self.module.running then
  63. -- run method once
  64. if self.module.called[self.module.index] == nil then
  65. self.module.called[self.module.index] = true
  66. local method = self.module.running[self.module.index]
  67. self.test = TestMethod:new(method, self.module)
  68. TextRun = 'love.' .. self.module.module .. '.' .. method
  69. self.test.co = coroutine.create(function()
  70. local ok, chunk, err = pcall(
  71. love.test[love.test.module.module][method],
  72. love.test.test
  73. )
  74. if ok == false then
  75. love.test.test['passed'] = false
  76. love.test.test['fatal'] = tostring(chunk) .. tostring(err)
  77. end
  78. end)
  79. -- once called we have a corouting, so just call resume every frame
  80. -- until we have finished
  81. else
  82. -- move onto next yield if any
  83. -- pauses can be set with TestMethod:waitFrames(frames)
  84. coroutine.resume(self.test.co)
  85. -- when wait finished (or no yields)
  86. if coroutine.status(self.test.co) == 'dead' then
  87. -- now we're all done evaluate the test
  88. local ok, chunk, err = pcall(self.test.evaluateTest, self.test)
  89. if ok == false then
  90. self.test.passed = false
  91. self.test.fatal = tostring(chunk) .. tostring(err)
  92. end
  93. -- save having to :release() anything we made in the last test
  94. collectgarbage("collect")
  95. -- move onto the next test
  96. self.module.index = self.module.index + 1
  97. end
  98. end
  99. -- once all tests have run
  100. else
  101. -- print module results and add to output
  102. self.module:printResult()
  103. -- if we have more modules to go run the next one
  104. self.current = self.current + 1
  105. if #self.modules >= self.current then
  106. self.module = self.modules[self.current]
  107. self.module:runTests()
  108. -- otherwise print the final results and export output
  109. else
  110. self:printResult()
  111. love.event.quit(0)
  112. end
  113. end
  114. end
  115. end
  116. end,
  117. -- @method - TestSuite:printResult()
  118. -- @desc - prints the result of the whole test suite as well as writes
  119. -- the MD, XML + HTML of the testsuite output
  120. -- @return {nil}
  121. printResult = function(self)
  122. local finaltime = UtilTimeFormat(self.time)
  123. -- in case we dont have love.graphics loaded, for future module specific disabling
  124. local name = 'NONE'
  125. local version = 'NONE'
  126. local vendor = 'NONE'
  127. local device = 'NONE'
  128. if love.graphics then
  129. name, version, vendor, device = love.graphics.getRendererInfo()
  130. end
  131. local md = '<!-- PASSED ' .. tostring(self.totals[1]) ..
  132. ' || FAILED ' .. tostring(self.totals[2]) ..
  133. ' || SKIPPED ' .. tostring(self.totals[3]) ..
  134. ' || TIME ' .. finaltime .. ' -->\n\n### Info\n' ..
  135. '**' .. tostring(self.totals[1] + self.totals[2] + self.totals[3]) .. '** tests were completed in **' ..
  136. finaltime .. 's** with **' ..
  137. tostring(self.totals[1]) .. '** passed, **' ..
  138. tostring(self.totals[2]) .. '** failed, and **' ..
  139. tostring(self.totals[3]) .. '** skipped\n\n' ..
  140. 'Renderer: ' .. name .. ' | ' .. version .. ' | ' .. vendor .. ' | ' .. device .. '\n\n' ..
  141. '### Report\n' ..
  142. '| Module | Pass | Fail | Skip | Time |\n' ..
  143. '| --------------------- | ------ | ------ | ------- | ------ |\n' ..
  144. self.mdrows .. '\n### Failures\n' .. self.mdfailures
  145. local xml = '<testsuites name="love.test" tests="' .. tostring(self.totals[1]) ..
  146. '" failures="' .. tostring(self.totals[2]) ..
  147. '" skipped="' .. tostring(self.totals[3]) ..
  148. '" time="' .. finaltime .. '">\n'
  149. local status = '<div class="icon fail"></div>'
  150. if self.totals[2] == 0 then status = '<div class="icon pass"></div>' end
  151. local html = [[
  152. <html>
  153. <head>
  154. <style>
  155. * { font-family: monospace; margin: 0; font-size: 11px; padding: 0; }
  156. body { margin: 40px 50px 50px 50px; overflow-y: scroll; background: #222; }
  157. h1 { font-weight: normal; color: #eee; font-size: 12px; width: 140px; border-radius: 2px; padding: 5px 0; float: left; background: #333; }
  158. h2 { font-weight: normal; color: #eee; font-size: 12px; width: 140px; border-radius: 2px; }
  159. .summary { z-index: 10; position: relative; list-style: none; margin: 0; padding: 0; float: right; }
  160. .summary li { color: #111; float: left; border-radius: 2px; background: #eee; padding: 5px; margin-right: 10px; text-align: right; }
  161. table { color: #eee; background: #444; margin: 5px 0 0 10px; width: calc(100% - 20px); max-width: 800px; border-collapse: collapse }
  162. table thead { background: #333; }
  163. table th, table td { padding: 2px 4px; font-size: 11px; }
  164. tr.red { background: #d26666; color: #111; }
  165. tr.yellow { background: slategrey; }
  166. .wrap { max-width: 800px; padding-top: 30px; margin: auto; position: relative; }
  167. .preview-wrap { display: inline-block; height: 80px; padding: 5px 0 0 5px; margin: 5px; background: rgba(0, 0, 0, 0.1); }
  168. .preview { width: 64px; height: 80px; float: left; margin-right: 10px; }
  169. .preview:nth-last-child(1) { margin-right: 5px; }
  170. .preview img { width: 100%; image-rendering: pixelated; }
  171. .preview p { text-align: center; }
  172. .module { margin-top: 10px; position: relative; }
  173. .module h2 { float: left; margin: 0; padding: 5px 0 0 35px; }
  174. .module .toggle { background: #2d9966; color: #111; left: 10px; width: 14px; border-radius: 2px; padding: 6px; text-align: center; cursor: pointer; position: absolute; }
  175. .module.fail .toggle { background: #d26666; }
  176. .module.fail h2 { color: #d26666; }
  177. .toggle.close ~ table { display: none; }
  178. .summary li:nth-child(1) { background: #2d9966; min-width: 70px; }
  179. .summary li:nth-child(2) { background: #d26666; min-width: 70px; }
  180. .summary li:nth-child(3) { background: slategrey; min-width: 70px; }
  181. .summary li:nth-child(4) { background: #bbb; min-width: 60px; }
  182. .summary li.l0 { opacity: 0.2; }
  183. .renderer { position: absolute; top: 8px; right: 10px; color: #eee; }
  184. h1 { width: 100%; top: 0; position: absolute; height: 50px; left: 0; }
  185. table .icon.pass { position: relative; width: 8px; height: 8px; border-radius: 8px; margin-left: 6px; }
  186. table .icon.pass:after { content: '✓'; top: -3px; position: absolute; color: #2d9966; font-size: 12px; }
  187. </style>
  188. <script type="text/javascript">
  189. function toggle(el) {
  190. el.className = el.className == 'toggle close' ? 'toggle open' : 'toggle close';
  191. el.innerText = el.className == 'toggle close' ? '▶' : '▼';
  192. }
  193. </script>
  194. </head>
  195. <body>]]
  196. local wrap_cls = ''
  197. if self.totals[2] > 0 then wrap_cls = 'fail' end
  198. html = html .. '<div class="wrap ' .. wrap_cls .. '"><h1>' .. status .. '&nbsp;love.test report</h1>' ..
  199. '<p class="renderer">Renderer: ' .. name .. ' | ' .. version .. ' | ' .. vendor .. ' | ' .. device .. '</p>' ..
  200. '<ul class="summary">'
  201. html = html ..
  202. '<li>' .. tostring(self.totals[1]) .. ' Passed</li>' ..
  203. '<li>' .. tostring(self.totals[2]) .. ' Failed</li>' ..
  204. '<li>' .. tostring(self.totals[3]) .. ' Skipped</li>' ..
  205. '<li>' .. finaltime .. 's</li></ul><br/><br/>'
  206. love.filesystem.write('tempoutput/' .. self.output .. '.xml', xml .. self.xml .. '</testsuites>')
  207. love.filesystem.write('tempoutput/' .. self.output .. '.html', html .. self.html .. '</div></body></html>')
  208. love.filesystem.write('tempoutput/' .. self.output .. '.md', md)
  209. self.module:log('grey', '\nFINISHED - ' .. finaltime .. 's\n')
  210. local failedcol = '\27[31m'
  211. if self.totals[2] == 0 then failedcol = '\27[37m' end
  212. self.module:log('green', tostring(self.totals[1]) .. ' PASSED' .. ' || ' .. failedcol .. tostring(self.totals[2]) .. ' FAILED || \27[37m' .. tostring(self.totals[3]) .. ' SKIPPED')
  213. end
  214. }