TestMethod.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. -- @class - TestMethod
  2. -- @desc - used to run a specific method from a module's /test/ suite
  3. -- each assertion is tracked and then printed to output
  4. TestMethod = {
  5. -- @method - TestMethod:new()
  6. -- @desc - create a new TestMethod object
  7. -- @param {string} method - string of method name to run
  8. -- @param {TestMethod} testmethod - parent testmethod this test belongs to
  9. -- @return {table} - returns the new Test object
  10. new = function(self, method, testmodule)
  11. local test = {
  12. testmodule = testmodule,
  13. method = method,
  14. asserts = {},
  15. start = love.timer.getTime(),
  16. finish = 0,
  17. count = 0,
  18. passed = false,
  19. skipped = false,
  20. skipreason = '',
  21. fatal = '',
  22. message = nil,
  23. result = {},
  24. colors = {
  25. red = {1, 0, 0, 1},
  26. green = {0, 1, 0, 1},
  27. blue = {0, 0, 1, 1},
  28. black = {0, 0, 0, 1},
  29. white = {1, 1, 1, 1}
  30. }
  31. }
  32. setmetatable(test, self)
  33. self.__index = self
  34. return test
  35. end,
  36. -- @method - TestMethod:assertEquals()
  37. -- @desc - used to assert two values are equals
  38. -- @param {any} expected - expected value of the test
  39. -- @param {any} actual - actual value of the test
  40. -- @param {string} label - label for this test to use in exports
  41. -- @return {nil}
  42. assertEquals = function(self, expected, actual, label)
  43. self.count = self.count + 1
  44. table.insert(self.asserts, {
  45. key = 'assert #' .. tostring(self.count),
  46. passed = expected == actual,
  47. message = 'expected \'' .. tostring(expected) .. '\' got \'' ..
  48. tostring(actual) .. '\'',
  49. test = label
  50. })
  51. end,
  52. -- @method - TestMethod:assertPixels()
  53. -- @desc - checks a list of coloured pixels agaisnt given imgdata
  54. -- @param {ImageData} imgdata - image data to check
  55. -- @param {table} pixels - map of colors to list of pixel coords, i.e.
  56. -- { blue = { {1, 1}, {2, 2}, {3, 4} } }
  57. -- @return {nil}
  58. assertPixels = function(self, imgdata, pixels, label)
  59. for i, v in pairs(pixels) do
  60. local col = self.colors[i]
  61. local pixels = v
  62. for p=1,#pixels do
  63. local coord = pixels[p]
  64. local tr, tg, tb, ta = imgdata:getPixel(coord[1], coord[2])
  65. local compare_id = tostring(coord[1]) .. ',' .. tostring(coord[2])
  66. -- @TODO add some sort pixel tolerance to the coords
  67. self:assertEquals(col[1], tr, 'check pixel r for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
  68. self:assertEquals(col[2], tg, 'check pixel g for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
  69. self:assertEquals(col[3], tb, 'check pixel b for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
  70. self:assertEquals(col[4], ta, 'check pixel a for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
  71. end
  72. end
  73. end,
  74. -- @method - TestMethod:assertNotEquals()
  75. -- @desc - used to assert two values are not equal
  76. -- @param {any} expected - expected value of the test
  77. -- @param {any} actual - actual value of the test
  78. -- @param {string} label - label for this test to use in exports
  79. -- @return {nil}
  80. assertNotEquals = function(self, expected, actual, label)
  81. self.count = self.count + 1
  82. table.insert(self.asserts, {
  83. key = 'assert #' .. tostring(self.count),
  84. passed = expected ~= actual,
  85. message = 'avoiding \'' .. tostring(expected) .. '\' got \'' ..
  86. tostring(actual) .. '\'',
  87. test = label
  88. })
  89. end,
  90. -- @method - TestMethod:assertRange()
  91. -- @desc - used to check a value is within an expected range
  92. -- @param {number} actual - actual value of the test
  93. -- @param {number} min - minimum value the actual should be >= to
  94. -- @param {number} max - maximum value the actual should be <= to
  95. -- @param {string} label - label for this test to use in exports
  96. -- @return {nil}
  97. assertRange = function(self, actual, min, max, label)
  98. self.count = self.count + 1
  99. table.insert(self.asserts, {
  100. key = 'assert #' .. tostring(self.count),
  101. passed = actual >= min and actual <= max,
  102. message = 'value \'' .. tostring(actual) .. '\' out of range \'' ..
  103. tostring(min) .. '-' .. tostring(max) .. '\'',
  104. test = label
  105. })
  106. end,
  107. -- @method - TestMethod:assertMatch()
  108. -- @desc - used to check a value is within a list of values
  109. -- @param {number} list - list of valid values for the test
  110. -- @param {number} actual - actual value of the test to check is in the list
  111. -- @param {string} label - label for this test to use in exports
  112. -- @return {nil}
  113. assertMatch = function(self, list, actual, label)
  114. self.count = self.count + 1
  115. local found = false
  116. for l=1,#list do
  117. if list[l] == actual then found = true end;
  118. end
  119. table.insert(self.asserts, {
  120. key = 'assert #' .. tostring(self.count),
  121. passed = found == true,
  122. message = 'value \'' .. tostring(actual) .. '\' not found in \'' ..
  123. table.concat(list, ',') .. '\'',
  124. test = label
  125. })
  126. end,
  127. -- @method - TestMethod:assertGreaterEqual()
  128. -- @desc - used to check a value is >= than a certain target value
  129. -- @param {any} target - value to check the test agaisnt
  130. -- @param {any} actual - actual value of the test
  131. -- @param {string} label - label for this test to use in exports
  132. -- @return {nil}
  133. assertGreaterEqual = function(self, target, actual, label)
  134. self.count = self.count + 1
  135. local passing = false
  136. if target ~= nil and actual ~= nil then
  137. passing = actual >= target
  138. end
  139. table.insert(self.asserts, {
  140. key = 'assert #' .. tostring(self.count),
  141. passed = passing,
  142. message = 'value \'' .. tostring(actual) .. '\' not >= \'' ..
  143. tostring(target) .. '\'',
  144. test = label
  145. })
  146. end,
  147. -- @method - TestMethod:assertLessEqual()
  148. -- @desc - used to check a value is <= than a certain target value
  149. -- @param {any} target - value to check the test agaisnt
  150. -- @param {any} actual - actual value of the test
  151. -- @param {string} label - label for this test to use in exports
  152. -- @return {nil}
  153. assertLessEqual = function(self, target, actual, label)
  154. self.count = self.count + 1
  155. local passing = false
  156. if target ~= nil and actual ~= nil then
  157. passing = actual <= target
  158. end
  159. table.insert(self.asserts, {
  160. key = 'assert #' .. tostring(self.count),
  161. passed = passing,
  162. message = 'value \'' .. tostring(actual) .. '\' not <= \'' ..
  163. tostring(target) .. '\'',
  164. test = label
  165. })
  166. end,
  167. -- @method - TestMethod:assertObject()
  168. -- @desc - used to check a table is a love object, this runs 3 seperate
  169. -- tests to check table has the basic properties of an object
  170. -- @note - actual object functionality tests are done in the objects module
  171. -- @param {table} obj - table to check is a valid love object
  172. -- @return {nil}
  173. assertObject = function(self, obj)
  174. self:assertNotEquals(nil, obj, 'check not nill')
  175. self:assertEquals('userdata', type(obj), 'check is userdata')
  176. if obj ~= nil then
  177. self:assertNotEquals(nil, obj:type(), 'check has :type()')
  178. end
  179. end,
  180. -- @method - TestMethod:skipTest()
  181. -- @desc - used to mark this test as skipped for a specific reason
  182. -- @param {string} reason - reason why method is being skipped
  183. -- @return {nil}
  184. skipTest = function(self, reason)
  185. self.skipped = true
  186. self.skipreason = reason
  187. end,
  188. -- @method - TestMethod:evaluateTest()
  189. -- @desc - evaluates the results of all assertions for a final restult
  190. -- @return {nil}
  191. evaluateTest = function(self)
  192. local failure = ''
  193. local failures = 0
  194. for a=1,#self.asserts do
  195. -- @TODO just return first failed assertion msg? or all?
  196. -- currently just shows the first assert that failed
  197. if self.asserts[a].passed == false and self.skipped == false then
  198. if failure == '' then failure = self.asserts[a] end
  199. failures = failures + 1
  200. end
  201. end
  202. if self.fatal ~= '' then failure = self.fatal end
  203. local passed = tostring(#self.asserts - failures)
  204. local total = '(' .. passed .. '/' .. tostring(#self.asserts) .. ')'
  205. if self.skipped == true then
  206. self.testmodule.skipped = self.testmodule.skipped + 1
  207. love.test.totals[3] = love.test.totals[3] + 1
  208. self.result = {
  209. total = '',
  210. result = "SKIP",
  211. passed = false,
  212. message = '(0/0) - method skipped [' .. self.skipreason .. ']'
  213. }
  214. else
  215. if failure == '' and #self.asserts > 0 then
  216. self.passed = true
  217. self.testmodule.passed = self.testmodule.passed + 1
  218. love.test.totals[1] = love.test.totals[1] + 1
  219. self.result = {
  220. total = total,
  221. result = 'PASS',
  222. passed = true,
  223. message = nil
  224. }
  225. else
  226. self.passed = false
  227. self.testmodule.failed = self.testmodule.failed + 1
  228. love.test.totals[2] = love.test.totals[2] + 1
  229. if #self.asserts == 0 then
  230. local msg = 'no asserts defined'
  231. if self.fatal ~= '' then msg = self.fatal end
  232. self.result = {
  233. total = total,
  234. result = 'FAIL',
  235. passed = false,
  236. key = 'test',
  237. message = msg
  238. }
  239. else
  240. local key = failure['key']
  241. if failure['test'] ~= nil then
  242. key = key .. ' [' .. failure['test'] .. ']'
  243. end
  244. self.result = {
  245. total = total,
  246. result = 'FAIL',
  247. passed = false,
  248. key = key,
  249. message = failure['message']
  250. }
  251. end
  252. end
  253. end
  254. self:printResult()
  255. end,
  256. -- @method - TestMethod:printResult()
  257. -- @desc - prints the result of the test to the console as well as appends
  258. -- the XML + HTML for the test to the testsuite output
  259. -- @return {nil}
  260. printResult = function(self)
  261. -- get total timestamp
  262. -- @TODO make nicer, just need a 3DP ms value
  263. self.finish = love.timer.getTime() - self.start
  264. love.test.time = love.test.time + self.finish
  265. self.testmodule.time = self.testmodule.time + self.finish
  266. local endtime = tostring(math.floor((love.timer.getTime() - self.start)*1000))
  267. if string.len(endtime) == 1 then endtime = ' ' .. endtime end
  268. if string.len(endtime) == 2 then endtime = ' ' .. endtime end
  269. if string.len(endtime) == 3 then endtime = ' ' .. endtime end
  270. -- get failure/skip message for output (if any)
  271. local failure = ''
  272. local output = ''
  273. if self.passed == false and self.skipped == false then
  274. failure = '\t\t\t<failure message="' .. self.result.key .. ' ' ..
  275. self.result.message .. '"></failure>\n'
  276. output = self.result.key .. ' ' .. self.result.message
  277. end
  278. if output == '' and self.skipped == true then
  279. output = self.skipreason
  280. end
  281. -- append XML for the test class result
  282. self.testmodule.xml = self.testmodule.xml .. '\t\t<testclass classname="' ..
  283. self.method .. '" name="' .. self.method ..
  284. '" time="' .. tostring(self.finish*1000) .. '">\n' ..
  285. failure .. '\t\t</testclass>\n'
  286. -- unused currently, adds a preview image for certain graphics methods to the output
  287. local preview = ''
  288. -- if self.testmodule.module == 'graphics' then
  289. -- local filename = 'love_test_graphics_rectangle'
  290. -- preview = '<div class="preview">' .. '<img src="' .. filename .. '_expected.png"/><p>Expected</p></div>' ..
  291. -- '<div class="preview"><img src="' .. filename .. '_actual.png"/><p>Actual</p></div>'
  292. -- end
  293. -- append HTML for the test class result
  294. local status = '🔴'
  295. local cls = 'red'
  296. if self.passed == true then status = '🟢'; cls = '' end
  297. if self.skipped == true then status = '🟡'; cls = '' end
  298. self.testmodule.html = self.testmodule.html ..
  299. '<tr class=" ' .. cls .. '">' ..
  300. '<td>' .. status .. '</td>' ..
  301. '<td>' .. self.method .. '</td>' ..
  302. '<td>' .. tostring(self.finish*1000) .. 'ms</td>' ..
  303. '<td>' .. output .. preview .. '</td>' ..
  304. '</tr>'
  305. -- add message if assert failed
  306. local msg = ''
  307. if self.result.message ~= nil and self.skipped == false then
  308. msg = ' - ' .. self.result.key ..
  309. ' failed - (' .. self.result.message .. ')'
  310. end
  311. if self.skipped == true then
  312. msg = self.result.message
  313. end
  314. -- log final test result to console
  315. -- i know its hacky but its neat soz
  316. local tested = 'love.' .. self.testmodule.module .. '.' .. self.method .. '()'
  317. local matching = string.sub(self.testmodule.spacer, string.len(tested), 40)
  318. self.testmodule:log(
  319. self.testmodule.colors[self.result.result],
  320. ' ' .. tested .. matching,
  321. ' ==> ' .. self.result.result .. ' - ' .. endtime .. 'ms ' ..
  322. self.result.total .. msg
  323. )
  324. end
  325. }