TestSuite.lua 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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. testcanvas = nil,
  11. current = 1,
  12. output = '',
  13. totals = {0, 0, 0},
  14. time = 0,
  15. xml = '',
  16. html = '',
  17. mdrows = '',
  18. mdfailures = '',
  19. fakequit = false,
  20. windowmode = true,
  21. -- love modules to test
  22. audio = {},
  23. data = {},
  24. event = {},
  25. filesystem = {},
  26. font = {},
  27. graphics = {},
  28. image = {},
  29. joystick = {},
  30. math = {},
  31. mouse = {},
  32. objects = {}, -- special for all object class contructor tests
  33. physics = {},
  34. sound = {},
  35. system = {},
  36. thread = {},
  37. timer = {},
  38. touch = {},
  39. video = {},
  40. window = {}
  41. }
  42. setmetatable(test, self)
  43. self.__index = self
  44. return test
  45. end,
  46. -- @method - TestSuite:runSuite()
  47. -- @desc - called in love.update, runs through every method or every module
  48. -- @param {number} delta - delta from love.update to track time elapsed
  49. -- @return {nil}
  50. runSuite = function(self, delta)
  51. -- stagger 0.1s between tests
  52. if self.module ~= nil then
  53. self.module.timer = self.module.timer + delta
  54. if self.module.timer >= self.module.delay then
  55. self.module.timer = self.module.timer - self.module.delay
  56. if self.module.start == true then
  57. -- work through each test method 1 by 1
  58. if self.module.index <= #self.module.running then
  59. -- run method once
  60. if self.module.called[self.module.index] == nil then
  61. self.module.called[self.module.index] = true
  62. local method = self.module.running[self.module.index]
  63. local test = TestMethod:new(method, self.module)
  64. -- check method exists in love first
  65. if self.module.module ~= 'objects' and (love[self.module.module] == nil or love[self.module.module][method] == nil) then
  66. local tested = 'love.' .. self.module.module .. '.' .. method .. '()'
  67. local matching = string.sub(self.module.spacer, string.len(tested), 40)
  68. self.module:log(self.module.colors['FAIL'],
  69. tested .. matching,
  70. ' ==> FAIL (0/0) - call failed - method does not exist'
  71. )
  72. -- otherwise run the test method then eval the asserts
  73. else
  74. local ok, chunk, err = pcall(self[self.module.module][method], test)
  75. if ok == false then
  76. print("FATAL", chunk, err)
  77. test.fatal = tostring(chunk) .. tostring(err)
  78. end
  79. local ok, chunk, err = pcall(test.evaluateTest, test)
  80. if ok == false then
  81. print("FATAL", chunk, err)
  82. test.fatal = tostring(chunk) .. tostring(err)
  83. end
  84. end
  85. -- save having to :release() anything we made in the last test
  86. -- 7251ms > 7543ms
  87. collectgarbage("collect")
  88. -- move onto the next test
  89. self.module.index = self.module.index + 1
  90. end
  91. else
  92. -- print module results and add to output
  93. self.module:printResult()
  94. -- if we have more modules to go run the next one
  95. self.current = self.current + 1
  96. if #self.modules >= self.current then
  97. self.module = self.modules[self.current]
  98. self.module:runTests()
  99. -- otherwise print the final results and export output
  100. else
  101. self:printResult()
  102. love.event.quit(0)
  103. end
  104. end
  105. end
  106. end
  107. end
  108. end,
  109. -- @method - TestSuite:printResult()
  110. -- @desc - prints the result of the whole test suite as well as writes
  111. -- the MD, XML + HTML of the testsuite output
  112. -- @return {nil}
  113. printResult = function(self)
  114. local finaltime = UtilTimeFormat(self.time)
  115. local md = '<!-- PASSED ' .. tostring(self.totals[1]) ..
  116. ' || FAILED ' .. tostring(self.totals[2]) ..
  117. ' || SKIPPED ' .. tostring(self.totals[3]) ..
  118. ' || TIME ' .. finaltime .. ' -->\n\n' ..
  119. '**' .. tostring(self.totals[1] + self.totals[2] + self.totals[3]) .. '** tests were completed in **' ..
  120. finaltime .. 's** with **' ..
  121. tostring(self.totals[1]) .. '** passed, **' ..
  122. tostring(self.totals[2]) .. '** failed, and **' ..
  123. tostring(self.totals[3]) .. '** skipped\n\n### Report\n' ..
  124. '| Module | Passed | Failed | Skipped | Time |\n' ..
  125. '| --------------------- | ------ | ------ | ------- | ------ |\n' ..
  126. self.mdrows .. '\n\n### Failures\n' .. self.mdfailures
  127. local xml = '<testsuites name="love.test" tests="' .. tostring(self.totals[1]) ..
  128. '" failures="' .. tostring(self.totals[2]) ..
  129. '" skipped="' .. tostring(self.totals[3]) ..
  130. '" time="' .. finaltime .. '">\n'
  131. local status = '🔴'
  132. if self.totals[2] == 0 then status = '🟢' end
  133. local html = '<html><head><style>* { font-family: monospace; margin: 0; font-size: 11px; padding: 0; } body { margin: 50px; } h1 { padding-bottom: 10px; font-size: 13px; } h2 { padding: 20px 0 10px 0; font-size: 12px; } .summary { list-style: none; margin: 0; padding: 0; } .summary li { float: left; background: #eee; padding: 5px; margin-right: 10px; } table { background: #eee; margin-top: 10px; width: 100%; max-width: 800px; border-collapse: collapse } table thead { background: #ddd; } table th, table td { padding: 2px; } tr.red { color: red } .wrap { max-width: 800px; margin: auto; } .preview { width: 64px; height: 80px; float: left; margin-right: 10px; } .preview img { width: 100% } .preview p { text-align: center; }</style></head><body><div class="wrap"><h1>' .. status .. '&nbsp;love.test</h1><ul class="summary">'
  134. html = html ..
  135. '<li>🟢&nbsp;' .. tostring(self.totals[1]) .. ' Tests</li>' ..
  136. '<li>🔴&nbsp;' .. tostring(self.totals[2]) .. ' Failures</li>' ..
  137. '<li>🟡&nbsp;' .. tostring(self.totals[3]) .. ' Skipped</li>' ..
  138. '<li>' .. finaltime .. 's</li></ul><br/><br/>'
  139. -- @TODO use mountFullPath to write output to src?
  140. love.filesystem.mountFullPath(love.filesystem.getSource() .. "/output", "tempoutput", "readwrite")
  141. love.filesystem.write('tempoutput/' .. self.output .. '.xml', xml .. self.xml .. '</testsuites>')
  142. love.filesystem.write('tempoutput/' .. self.output .. '.html', html .. self.html .. '</div></body></html>')
  143. love.filesystem.write('tempoutput/' .. self.output .. '.md', md)
  144. self.module:log('grey', '\nFINISHED - ' .. finaltime .. 's\n')
  145. local failedcol = '\27[31m'
  146. if self.totals[2] == 0 then failedcol = '\27[37m' end
  147. self.module:log('green', tostring(self.totals[1]) .. ' PASSED' .. ' || ' .. failedcol .. tostring(self.totals[2]) .. ' FAILED || \27[37m' .. tostring(self.totals[3]) .. ' SKIPPED')
  148. end
  149. }