diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f3922d769..a6da63566 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -3,7 +3,13 @@ on: [push, pull_request]
jobs:
linux-os:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
+ permissions:
+ checks: write
+ pull-requests: write
+ env:
+ ALSOFT_CONF: love2d-${{ github.sha }}/testing/resources/alsoft.conf
+ DISPLAY: :99
steps:
- name: Update APT
run: sudo apt-get update
@@ -16,7 +22,8 @@ jobs:
libgl1-mesa-dev libdbus-1-dev libudev-dev libgles2-mesa-dev \
libegl1-mesa-dev libibus-1.0-dev fcitx-libs-dev libsamplerate0-dev \
libsndio-dev libwayland-dev libxkbcommon-dev libdrm-dev libgbm-dev \
- libcurl4-openssl-dev
+ libcurl4-openssl-dev libfuse2 wmctrl openbox mesa-vulkan-drivers \
+ libvulkan1 vulkan-tools vulkan-validationlayers
- name: Checkout love-appimage-source
uses: actions/checkout@v3
with:
@@ -40,6 +47,83 @@ jobs:
run: make LOVE_BRANCH=${{ github.sha }}
- name: Print LuaJIT branch
run: git -C LuaJIT-v2.1 branch -v
+ # start xvfb for test running
+ - name: Start xvfb and openbox
+ run: |
+ echo "Starting XVFB on $DISPLAY"
+ Xvfb $DISPLAY -screen 0, 360x240x24 &
+ echo "XVFBPID=$!" >> $GITHUB_ENV
+ # wait for xvfb to startup (3s is the same amount xvfb-run waits by default)
+ sleep 3
+ openbox &
+ echo "OPENBOXPID=$!" >> $GITHUB_ENV
+ # linux opengl tests
+ - name: Run Test Suite (opengl)
+ run: |
+ chmod a+x love-${{ github.sha }}.AppImage
+ ./love-${{ github.sha }}.AppImage love2d-${{ github.sha }}/testing/main.lua --runAllTests --isRunner
+ - name: Love Test Report (opengl)
+ id: report1
+ uses: ellraiser/love-test-report@main
+ with:
+ name: Love Testsuite Linux
+ title: test-report-linux-opengl
+ path: love2d-${{ github.sha }}/testing/output/lovetest_runAllTests.md
+ token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Zip Test Output (opengl)
+ run: |
+ 7z a -tzip test-output-linux-opengl.zip love2d-${{ github.sha }}/testing/output/
+ - name: Artifact Test Output (opengl)
+ uses: actions/upload-artifact@v3
+ with:
+ name: test-output-linux-opengl-${{ steps.report1.outputs.conclusion }}
+ path: test-output-linux-opengl.zip
+ # linux opengles tests
+ - name: Run Test Suite (opengles)
+ run: |
+ export LOVE_GRAPHICS_USE_OPENGLES=1
+ ./love-${{ github.sha }}.AppImage love2d-${{ github.sha }}/testing/main.lua --runAllTests --isRunner
+ - name: Love Test Report (opengles)
+ uses: ellraiser/love-test-report@main
+ id: report2
+ with:
+ name: Love Testsuite Linux
+ title: test-report-linux-opengles
+ path: love2d-${{ github.sha }}/testing/output/lovetest_runAllTests.md
+ token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Zip Test Output (opengles)
+ run: |
+ 7z a -tzip test-output-linux-opengles.zip love2d-${{ github.sha }}/testing/output/
+ - name: Artifact Test Output (opengles)
+ uses: actions/upload-artifact@v3
+ with:
+ name: test-output-linux-opengles-${{ steps.report2.outputs.conclusion }}
+ path: test-output-linux-opengles.zip
+# # linux vulkan tests
+# - name: Run Test Suite (vulkan)
+# run: |
+# export LOVE_GRAPHICS_DEBUG=1
+# ./love-${{ github.sha }}.AppImage love2d-${{ github.sha }}/testing/main.lua --runAllTests --isRunner --renderers vulkan
+# - name: Love Test Report (vulkan)
+# uses: ellraiser/love-test-report@main
+# with:
+# name: Love Testsuite Linux
+# title: test-report-linux-vulkan
+# path: love2d-${{ github.sha }}/testing/output/lovetest_runAllTests.md
+# - name: Zip Test Output (vulkan)
+# run: |
+# 7z a -tzip test-output-linux-vulkan.zip love2d-${{ github.sha }}/testing/output/
+# - name: Artifact Test Output (vulkan)
+# uses: actions/upload-artifact@v3
+# with:
+# name: test-output-linux-vulkan
+# path: test-output-linux-vulkan.zip
+ - name: Stop xvfb and openbox
+ # should always stop xvfb and openbox even if other steps failed
+ if: always()
+ run: |
+ kill $XVFBPID
+ kill $OPENBOXPID
- name: Artifact
uses: actions/upload-artifact@v3
with:
@@ -50,8 +134,21 @@ jobs:
with:
name: love-x86_64-AppImage-debug
path: love-${{ github.sha }}.AppImage-debug.tar.gz
+ - name: Check Tests Passing
+ if: steps.report1.outputs.conclusion == 'failure' || steps.report2.outputs.conclusion == 'failure'
+ run: |
+ echo "${{ steps.report1.outputs.failed }} opengl tests failed"
+ echo "${{ steps.report2.outputs.failed }} opengles tests failed"
+ exit 1
windows-os:
runs-on: windows-latest
+ permissions:
+ checks: write
+ pull-requests: write
+ env:
+ ALSOFT_CONF: megasource/libs/love/testing/resources/alsoft.conf
+ VK_ICD_FILENAMES: ${{ github.workspace }}\mesa\x64\lvp_icd.x86_64.json
+ VULKAN_SDK: C:/VulkanSDK/1.3.231.1
strategy:
matrix:
platform: [Win32, x64, ARM64]
@@ -206,8 +303,115 @@ jobs:
with:
name: love-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-dbg
path: pdb/Release/*.pdb
+ # install mesa for graphic tests
+ - name: Install Mesa
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ run: |
+ curl -L --output mesa.7z --url https://github.com/pal1000/mesa-dist-win/releases/download/23.2.1/mesa3d-23.2.1-release-msvc.7z
+ 7z x mesa.7z -o*
+ powershell.exe mesa\systemwidedeploy.cmd 1
+ # build love to use for the tests
+ - name: Build Test Exe
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ run: cmake --build build --config Release --target install
+ # windows opengl tests
+ - name: Run Tests (opengl)
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ run: |
+ echo 'check dir'
+ ls
+ powershell.exe ./install/lovec.exe ./megasource/libs/love/testing/main.lua --runAllTests --isRunner
+ - name: Love Test Report (opengl)
+ id: report1
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ uses: ellraiser/love-test-report@main
+ with:
+ name: Love Testsuite Windows ${{ steps.vars.outputs.arch }} ${{ steps.vars.outputs.compatname }} (opengl)
+ title: test-report-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-opengl
+ path: megasource/libs/love/testing/output/lovetest_runAllTests.md
+ token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Zip Test Output (opengl)
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ run: |
+ 7z a -tzip test-output-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-opengl.zip megasource/libs/love/testing/output/
+ - name: Artifact Test Output (opengl)
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ uses: actions/upload-artifact@v3
+ with:
+ name: test-output-windows-${{ steps.vars.outputs.arch }}-opengl-${{ steps.report1.outputs.conclusion }}
+ path: test-output-windows-${{ steps.vars.outputs.arch }}-opengl.zip
+ # windows opengles tests
+ - name: Run Tests (opengles)
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ run: |
+ $ENV:LOVE_GRAPHICS_USE_OPENGLES=1
+ powershell.exe ./install/lovec.exe ./megasource/libs/love/testing/main.lua --runAllTests --isRunner
+ - name: Love Test Report (opengles)
+ id: report2
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ uses: ellraiser/love-test-report@main
+ with:
+ name: Love Testsuite Windows ${{ steps.vars.outputs.arch }} ${{ steps.vars.outputs.compatname }} (opengles)
+ title: test-report-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-opengles
+ path: megasource/libs/love/testing/output/lovetest_runAllTests.md
+ token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Zip Test Output (opengles)
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ run: |
+ 7z a -tzip test-output-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-opengles.zip megasource/libs/love/testing/output/
+ - name: Artifact Test Output (opengles)
+ if: steps.vars.outputs.arch != 'ARM64' && steps.vars.outputs.compatname != '-compat'
+ uses: actions/upload-artifact@v3
+ with:
+ name: test-output-windows-${{ steps.vars.outputs.arch }}-opengles-${{ steps.report2.outputs.conclusion }}
+ path: test-output-windows-${{ steps.vars.outputs.arch }}-opengles.zip
+ - name: Check Tests Passing
+ if: steps.report1.outputs.conclusion == 'failure' || steps.report2.outputs.conclusion == 'failure'
+ run: |
+ echo "${{ steps.report1.outputs.failed }} opengl tests failed"
+ echo "${{ steps.report2.outputs.failed }} opengles tests failed"
+ exit 1
+# # install vulkan
+# - name: Install Vulkan
+# if: steps.vars.outputs.arch != 'ARM64'
+# run: |
+# curl -L --show-error --output VulkanSDK.exe https://sdk.lunarg.com/sdk/download/1.3.231.1/windows/VulkanSDK-1.3.231.1-Installer.exe
+# ./VulkanSDK.exe --root C:/VulkanSDK/1.3.231.1 --accept-licenses --default-answer --confirm-command install com.lunarg.vulkan.core com.lunarg.vulkan.vma
+# curl -L --show-error --output vulkan-runtime.zip https://sdk.lunarg.com/sdk/download/1.3.231.1/windows/vulkan-runtime-components.zip
+# 7z e vulkan-runtime.zip -o"C:/VulkanSDK/1.3.231.1/runtime/x64" */x64
+# copy "C:/VulkanSDK/1.3.231.1/runtime/x64/vulkan-1.dll" "mesa/x64"
+# copy "C:/VulkanSDK/1.3.231.1/runtime/x64/vulkan-1.dll" "C:/Windows/System32"
+# copy "C:/VulkanSDK/1.3.231.1/runtime/x64/vulkan-1.dll" "love-12.0-win64/love-12.0-win64"
+# reg add HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\Drivers /v "${{ github.workspace }}\mesa\x64\lvp_icd.x86_64.json" /t REG_DWORD /d 0
+# powershell.exe C:/VulkanSDK/1.3.231.1/runtime/x64/vulkaninfo.exe --summary
+# # windows vulkan tests
+# - name: Run Tests (vulkan)
+# if: steps.vars.outputs.arch != 'ARM64'
+# run: |
+# $ENV:LOVE_GRAPHICS_DEBUG=1
+# powershell.exe ./install/lovec.exe ./megasource/libs/love/testing/main.lua --runAllTests --isRunner --renderers vulkan
+# - name: Love Test Report (vulkan)
+# if: steps.vars.outputs.arch != 'ARM64'
+# uses: ellraiser/love-test-report@main
+# with:
+# name: Love Testsuite Windows ${{ steps.vars.outputs.arch }} ${{ steps.vars.outputs.compatname }} (vulkan)
+# title: test-report-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-vulkan
+# path: megasource/libs/love/testing/output/lovetest_runAllTests.md
+# - name: Zip Test Output (vulkan)
+# if: steps.vars.outputs.arch != 'ARM64'
+# run: |
+# 7z a -tzip test-output-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-vulkan.zip megasource/libs/love/testing/output/
+# - name: Artifact Test Output (vulkan)
+# if: steps.vars.outputs.arch != 'ARM64'
+# uses: actions/upload-artifact@v3
+# with:
+# name: test-output-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-vulkan
+# path: test-output-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-vulkan.zip
macOS:
runs-on: macos-latest
+ permissions:
+ checks: write
+ pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -234,6 +438,32 @@ jobs:
with:
name: love-macos
path: love-macos.zip
+ # macos opengl tests (metal not supported on runners)
+ - name: Run Test Suite
+ run: |
+ ls
+ love-macos/love.app/Contents/MacOS/love ./testing/main.lua --runAllTests --isRunner
+ - name: Love Test Report
+ id: report1
+ uses: ellraiser/love-test-report@main
+ with:
+ name: Love Testsuite MacOS
+ title: test-report-macos
+ path: testing/output/lovetest_runAllTests.md
+ token: ${{ secrets.GITHUB_TOKEN }}
+ - name: Zip Test Output
+ run: |
+ 7z a -tzip test-output-macos-opengl.zip ./testing/output/
+ - name: Artifact Test Output
+ uses: actions/upload-artifact@v3
+ with:
+ name: test-output-macos-opengl-${{ steps.report1.outputs.conclusion }}
+ path: test-output-macos-opengl.zip
+ - name: Check Tests Passing
+ if: steps.report1.outputs.conclusion == 'failure'
+ run: |
+ echo "${{ steps.report1.outputs.failed }} opengl tests failed"
+ exit 1
iOS-Simulator:
runs-on: macos-latest
steps:
diff --git a/.gitignore b/.gitignore
index a8b316a2d..3f5288d45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,4 +68,8 @@ stamp-h1
/src/love
/src/tags
.vs/
-.vscode/
\ No newline at end of file
+.vscode/
+/testing/output/*.xml
+/testing/output/*.html
+/testing/output/*.md
+/testing/output/actual/*.png
diff --git a/testing/classes/TestMethod.lua b/testing/classes/TestMethod.lua
new file mode 100644
index 000000000..816c6bd74
--- /dev/null
+++ b/testing/classes/TestMethod.lua
@@ -0,0 +1,540 @@
+-- @class - TestMethod
+-- @desc - used to run a specific method from a module's /test/ suite
+-- each assertion is tracked and then printed to output
+TestMethod = {
+
+
+ -- @method - TestMethod:new()
+ -- @desc - create a new TestMethod object
+ -- @param {string} method - string of method name to run
+ -- @param {TestMethod} testmethod - parent testmethod this test belongs to
+ -- @return {table} - returns the new Test object
+ new = function(self, method, testmodule)
+ local test = {
+ testmodule = testmodule,
+ method = method,
+ asserts = {},
+ start = love.timer.getTime(),
+ finish = 0,
+ count = 0,
+ passed = false,
+ skipped = false,
+ skipreason = '',
+ rgba_tolerance = 0,
+ pixel_tolerance = 0,
+ fatal = '',
+ message = nil,
+ result = {},
+ colors = {
+ red = {1, 0, 0, 1},
+ redpale = {1, 0.5, 0.5, 1},
+ red07 = {0.7, 0, 0, 1},
+ green = {0, 1, 0, 1},
+ greenhalf = {0, 0.5, 0, 1},
+ greenfade = {0, 1, 0, 0.5},
+ blue = {0, 0, 1, 1},
+ bluefade = {0, 0, 1, 0.5},
+ yellow = {1, 1, 0, 1},
+ pink = {1, 0, 1, 1},
+ black = {0, 0, 0, 1},
+ white = {1, 1, 1, 1},
+ lovepink = {214/255, 86/255, 151/255, 1},
+ loveblue = {83/255, 168/255, 220/255, 1}
+ },
+ imgs = 1,
+ delay = 0,
+ delayed = false,
+ store = {},
+ co = nil
+ }
+ setmetatable(test, self)
+ self.__index = self
+ return test
+ end,
+
+
+ -- @method - TestMethod:assertEquals()
+ -- @desc - used to assert two values are equals
+ -- @param {any} expected - expected value of the test
+ -- @param {any} actual - actual value of the test
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertEquals = function(self, expected, actual, label)
+ self.count = self.count + 1
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = expected == actual,
+ message = 'expected \'' .. tostring(expected) .. '\' got \'' ..
+ tostring(actual) .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertTrue()
+ -- @desc - used to assert a value is true
+ -- @param {any} value - value to test
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertTrue = function(self, value, label)
+ self.count = self.count + 1
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = value == true,
+ message = 'expected \'true\' got \'' ..
+ tostring(value) .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertFalse()
+ -- @desc - used to assert a value is false
+ -- @param {any} value - value to test
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertFalse = function(self, value, label)
+ self.count = self.count + 1
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = value == false,
+ message = 'expected \'false\' got \'' ..
+ tostring(value) .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertNotEquals()
+ -- @desc - used to assert two values are not equal
+ -- @param {any} expected - expected value of the test
+ -- @param {any} actual - actual value of the test
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertNotEquals = function(self, expected, actual, label)
+ self.count = self.count + 1
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = expected ~= actual,
+ message = 'avoiding \'' .. tostring(expected) .. '\' got \'' ..
+ tostring(actual) .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertPixels()
+ -- @desc - checks a list of coloured pixels agaisnt given imgdata
+ -- @param {ImageData} imgdata - image data to check
+ -- @param {table} pixels - map of colors to list of pixel coords, i.e.
+ -- { blue = { {1, 1}, {2, 2}, {3, 4} } }
+ -- @return {nil}
+ assertPixels = function(self, imgdata, pixels, label)
+ for i, v in pairs(pixels) do
+ local col = self.colors[i]
+ local pixels = v
+ for p=1,#pixels do
+ local coord = pixels[p]
+ local tr, tg, tb, ta = imgdata:getPixel(coord[1], coord[2])
+ local compare_id = tostring(coord[1]) .. ',' .. tostring(coord[2])
+ -- prevent us getting stuff like 0.501960785 for 0.5 red
+ tr = math.floor((tr*10)+0.5)/10
+ tg = math.floor((tg*10)+0.5)/10
+ tb = math.floor((tb*10)+0.5)/10
+ ta = math.floor((ta*10)+0.5)/10
+ col[1] = math.floor((col[1]*10)+0.5)/10
+ col[2] = math.floor((col[2]*10)+0.5)/10
+ col[3] = math.floor((col[3]*10)+0.5)/10
+ col[4] = math.floor((col[4]*10)+0.5)/10
+ self:assertEquals(col[1], tr, 'check pixel r for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
+ self:assertEquals(col[2], tg, 'check pixel g for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
+ self:assertEquals(col[3], tb, 'check pixel b for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
+ self:assertEquals(col[4], ta, 'check pixel a for ' .. i .. ' at ' .. compare_id .. '(' .. label .. ')')
+ end
+ end
+ end,
+
+
+ -- @method - TestMethod:assertRange()
+ -- @desc - used to check a value is within an expected range
+ -- @param {number} actual - actual value of the test
+ -- @param {number} min - minimum value the actual should be >= to
+ -- @param {number} max - maximum value the actual should be <= to
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertRange = function(self, actual, min, max, label)
+ self.count = self.count + 1
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = actual >= min and actual <= max,
+ message = 'value \'' .. tostring(actual) .. '\' out of range \'' ..
+ tostring(min) .. '-' .. tostring(max) .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertMatch()
+ -- @desc - used to check a value is within a list of values
+ -- @param {number} list - list of valid values for the test
+ -- @param {number} actual - actual value of the test to check is in the list
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertMatch = function(self, list, actual, label)
+ self.count = self.count + 1
+ local found = false
+ for l=1,#list do
+ if list[l] == actual then found = true end;
+ end
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = found == true,
+ message = 'value \'' .. tostring(actual) .. '\' not found in \'' ..
+ table.concat(list, ',') .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertGreaterEqual()
+ -- @desc - used to check a value is >= than a certain target value
+ -- @param {any} target - value to check the test agaisnt
+ -- @param {any} actual - actual value of the test
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertGreaterEqual = function(self, target, actual, label)
+ self.count = self.count + 1
+ local passing = false
+ if target ~= nil and actual ~= nil then
+ passing = actual >= target
+ end
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = passing,
+ message = 'value \'' .. tostring(actual) .. '\' not >= \'' ..
+ tostring(target) .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertLessEqual()
+ -- @desc - used to check a value is <= than a certain target value
+ -- @param {any} target - value to check the test agaisnt
+ -- @param {any} actual - actual value of the test
+ -- @param {string} label - label for this test to use in exports
+ -- @return {nil}
+ assertLessEqual = function(self, target, actual, label)
+ self.count = self.count + 1
+ local passing = false
+ if target ~= nil and actual ~= nil then
+ passing = actual <= target
+ end
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = passing,
+ message = 'value \'' .. tostring(actual) .. '\' not <= \'' ..
+ tostring(target) .. '\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertObject()
+ -- @desc - used to check a table is a love object, this runs 3 seperate
+ -- tests to check table has the basic properties of an object
+ -- @note - actual object functionality tests have their own methods
+ -- @param {table} obj - table to check is a valid love object
+ -- @return {nil}
+ assertObject = function(self, obj)
+ self:assertNotNil(obj)
+ self:assertEquals('userdata', type(obj), 'check is userdata')
+ if obj ~= nil then
+ self:assertNotEquals(nil, obj:type(), 'check has :type()')
+ end
+ end,
+
+
+ -- @method - TestMethod:assertCoords()
+ -- @desc - used to check a pair of values (usually coordinates)
+ -- @param {table} obj - table to check is a valid love object
+ -- @return {nil}
+ assertCoords = function(self, expected, actual, label)
+ self.count = self.count + 1
+ local passing = false
+ if expected ~= nil and actual ~= nil then
+ if expected[1] == actual[1] and expected[2] == actual[2] then
+ passing = true
+ end
+ end
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = passing,
+ message = 'expected \'' .. tostring(expected[1]) .. 'x,' ..
+ tostring(expected[2]) .. 'y\' got \'' ..
+ tostring(actual[1]) .. 'x,' .. tostring(actual[2]) .. 'y\'',
+ test = label or 'no label given'
+ })
+ end,
+
+
+ -- @method - TestMethod:assertNotNil()
+ -- @desc - quick assert for value not nil
+ -- @param {any} value - value to check not nil
+ -- @return {nil}
+ assertNotNil = function (self, value, err)
+ self:assertNotEquals(nil, value, 'check not nil')
+ if err ~= nil then
+ table.insert(self.asserts, {
+ key = 'assert ' .. tostring(self.count),
+ passed = false,
+ message = err,
+ test = 'assert not nil catch'
+ })
+ end
+ end,
+
+
+ -- @method - TestMethod:compareImg()
+ -- @desc - compares a given image to the 'expected' version, with a tolerance of
+ -- 1px in any direction, and then saves it as the 'actual' version for
+ -- report viewing
+ -- @param {table} imgdata - imgdata to save as a png
+ -- @return {nil}
+ compareImg = function(self, imgdata)
+ local expected = love.image.newImageData(
+ 'tempoutput/expected/love.test.graphics.' .. self.method .. '-' ..
+ tostring(self.imgs) .. '.png'
+ )
+ local iw = imgdata:getWidth()-2
+ local ih = imgdata:getHeight()-2
+ local rgba_tolerance = self.rgba_tolerance * (1/255)
+ for ix=2,iw do
+ for iy=2,ih do
+ local ir, ig, ib, ia = imgdata:getPixel(ix, iy)
+ local points = {
+ {expected:getPixel(ix, iy)}
+ }
+ if self.pixel_tolerance > 0 then
+ table.insert(points, {expected:getPixel(ix-1, iy+1)})
+ table.insert(points, {expected:getPixel(ix-1, iy)})
+ table.insert(points, {expected:getPixel(ix-1, iy-1)})
+ table.insert(points, {expected:getPixel(ix, iy+1)})
+ table.insert(points, {expected:getPixel(ix, iy-1)})
+ table.insert(points, {expected:getPixel(ix+1, iy+1)})
+ table.insert(points, {expected:getPixel(ix+1, iy)})
+ table.insert(points, {expected:getPixel(ix+1, iy-1)})
+ end
+ local has_match_r = false
+ local has_match_g = false
+ local has_match_b = false
+ local has_match_a = false
+ for t=1,#points do
+ local epoint = points[t]
+ if ir >= epoint[1] - rgba_tolerance and ir <= epoint[1] + rgba_tolerance then has_match_r = true; end
+ if ig >= epoint[2] - rgba_tolerance and ig <= epoint[2] + rgba_tolerance then has_match_g = true; end
+ if ib >= epoint[3] - rgba_tolerance and ib <= epoint[3] + rgba_tolerance then has_match_b = true; end
+ if ia >= epoint[4] - rgba_tolerance and ia <= epoint[4] + rgba_tolerance then has_match_a = true; end
+ end
+ local matching = has_match_r and has_match_g and has_match_b and has_match_a
+ local ymatch = ''
+ local nmatch = ''
+ if has_match_r then ymatch = ymatch .. 'r' else nmatch = nmatch .. 'r' end
+ if has_match_g then ymatch = ymatch .. 'g' else nmatch = nmatch .. 'g' end
+ if has_match_b then ymatch = ymatch .. 'b' else nmatch = nmatch .. 'b' end
+ if has_match_a then ymatch = ymatch .. 'a' else nmatch = nmatch .. 'a' end
+ local pixel = tostring(ir)..','..tostring(ig)..','..tostring(ib)..','..tostring(ia)
+ self:assertEquals(true, matching, 'compare image pixel (' .. pixel .. ') at ' ..
+ tostring(ix) .. ',' .. tostring(iy) .. ', matching = ' .. ymatch ..
+ ', not matching = ' .. nmatch .. ' (' .. self.method .. '-' .. tostring(self.imgs) .. ')'
+ )
+ end
+ end
+ local path = 'tempoutput/actual/love.test.graphics.' ..
+ self.method .. '-' .. tostring(self.imgs) .. '.png'
+ imgdata:encode('png', path)
+ self.imgs = self.imgs + 1
+ end,
+
+
+ -- @method - TestMethod:skipTest()
+ -- @desc - used to mark this test as skipped for a specific reason
+ -- @param {string} reason - reason why method is being skipped
+ -- @return {nil}
+ skipTest = function(self, reason)
+ self.skipped = true
+ self.skipreason = reason
+ end,
+
+
+ waitFrames = function(self, frames)
+ for i=1,frames do coroutine.yield() end
+ end,
+
+
+ -- @method - TestMethod:evaluateTest()
+ -- @desc - evaluates the results of all assertions for a final restult
+ -- @return {nil}
+ evaluateTest = function(self)
+ local failure = ''
+ local failures = 0
+ for a=1,#self.asserts do
+ -- @TODO just return first failed assertion msg? or all?
+ -- currently just shows the first assert that failed
+ if self.asserts[a].passed == false and self.skipped == false then
+ if failure == '' then failure = self.asserts[a] end
+ failures = failures + 1
+ end
+ end
+ if self.fatal ~= '' then failure = self.fatal end
+ local passed = tostring(#self.asserts - failures)
+ local total = '(' .. passed .. '/' .. tostring(#self.asserts) .. ')'
+ if self.skipped == true then
+ self.testmodule.skipped = self.testmodule.skipped + 1
+ love.test.totals[3] = love.test.totals[3] + 1
+ self.result = {
+ total = '',
+ result = "SKIP",
+ passed = false,
+ message = '(0/0) - method skipped [' .. self.skipreason .. ']'
+ }
+ else
+ if failure == '' and #self.asserts > 0 then
+ self.passed = true
+ self.testmodule.passed = self.testmodule.passed + 1
+ love.test.totals[1] = love.test.totals[1] + 1
+ self.result = {
+ total = total,
+ result = 'PASS',
+ passed = true,
+ message = nil
+ }
+ else
+ self.passed = false
+ self.testmodule.failed = self.testmodule.failed + 1
+ love.test.totals[2] = love.test.totals[2] + 1
+ if #self.asserts == 0 then
+ local msg = 'no asserts defined'
+ if self.fatal ~= '' then msg = self.fatal end
+ self.result = {
+ total = total,
+ result = 'FAIL',
+ passed = false,
+ key = 'test',
+ message = msg
+ }
+ else
+ local key = failure['key']
+ if failure['test'] ~= nil then
+ key = key .. ' [' .. failure['test'] .. ']'
+ end
+ local msg = failure['message']
+ if self.fatal ~= '' then
+ key = 'code'
+ msg = self.fatal
+ end
+ self.result = {
+ total = total,
+ result = 'FAIL',
+ passed = false,
+ key = key,
+ message = msg
+ }
+ end
+ end
+ end
+ self:printResult()
+ end,
+
+
+ -- @method - TestMethod:printResult()
+ -- @desc - prints the result of the test to the console as well as appends
+ -- the XML + HTML for the test to the testsuite output
+ -- @return {nil}
+ printResult = function(self)
+
+ -- get total timestamp
+ -- @TODO make nicer, just need a 3DP ms value
+ self.finish = love.timer.getTime() - self.start
+ love.test.time = love.test.time + self.finish
+ self.testmodule.time = self.testmodule.time + self.finish
+ local endtime = UtilTimeFormat(love.timer.getTime() - self.start)
+
+ -- get failure/skip message for output (if any)
+ local failure = ''
+ local output = ''
+ if self.passed == false and self.skipped == false then
+ failure = '\t\t\t' .. self.result.key .. ' ' .. self.result.message .. '\n'
+ output = self.result.key .. ' ' .. self.result.message
+ -- append failures if any to report md
+ love.test.mdfailures = love.test.mdfailures .. '> 🔴 ' .. self.method .. ' \n' ..
+ '> ' .. output .. ' \n\n'
+ end
+ if output == '' and self.skipped == true then
+ failure = '\t\t\t\n'
+ output = self.skipreason
+ end
+
+
+ -- append XML for the test class result
+ self.testmodule.xml = self.testmodule.xml .. '\t\t\n' ..
+ failure .. '\t\t\n'
+
+ -- unused currently, adds a preview image for certain graphics methods to the output
+ local preview = ''
+ if self.testmodule.module == 'graphics' then
+ local filename = 'love.test.graphics.' .. self.method
+ if love.filesystem.openFile('tempoutput/actual/' .. filename .. '-1.png', 'r') then
+ preview = '
' .. '
Expected
' ..
+ '' .. '
Actual
'
+ end
+ if love.filesystem.openFile('tempoutput/actual/' .. filename .. '-2.png', 'r') then
+ preview = preview .. '' .. '
Expected
' ..
+ '' .. '
Actual
'
+ end
+ if love.filesystem.openFile('tempoutput/actual/' .. filename .. '-3.png', 'r') then
+ preview = preview .. '' .. '
Expected
' ..
+ '' .. '
Actual
'
+ end
+ end
+
+ -- append HTML for the test class result
+ local status = '🔴'
+ local cls = 'red'
+ if self.passed == true then status = '🟢'; cls = '' end
+ if self.skipped == true then status = '🟡'; cls = '' end
+ self.testmodule.html = self.testmodule.html ..
+ '' ..
+ '' .. status .. ' | ' ..
+ '' .. self.method .. ' | ' ..
+ '' .. endtime .. 's | ' ..
+ '' .. output .. preview .. ' | ' ..
+ '
'
+
+ -- add message if assert failed
+ local msg = ''
+ if self.result.message ~= nil and self.skipped == false then
+ msg = ' - ' .. self.result.key ..
+ ' failed - (' .. self.result.message .. ')'
+ end
+ if self.skipped == true then
+ msg = self.result.message
+ end
+
+ -- log final test result to console
+ -- i know its hacky but its neat soz
+ local tested = 'love.' .. self.testmodule.module .. '.' .. self.method .. '()'
+ local matching = string.sub(self.testmodule.spacer, string.len(tested), 40)
+ self.testmodule:log(
+ self.testmodule.colors[self.result.result],
+ ' ' .. tested .. matching,
+ ' ==> ' .. self.result.result .. ' - ' .. endtime .. 's ' ..
+ self.result.total .. msg
+ )
+ end
+
+
+}
diff --git a/testing/classes/TestModule.lua b/testing/classes/TestModule.lua
new file mode 100644
index 000000000..f66d247ed
--- /dev/null
+++ b/testing/classes/TestModule.lua
@@ -0,0 +1,120 @@
+-- @class - TestModule
+-- @desc - used to run tests for a given module, each test method will spawn
+-- a love.test.Test object
+TestModule = {
+
+
+ -- @method - TestModule:new()
+ -- @desc - create a new Suite object
+ -- @param {string} module - string of love module the suite is for
+ -- @return {table} - returns the new Suite object
+ new = function(self, module, method)
+ local testmodule = {
+ time = 0,
+ spacer = ' ',
+ colors = {
+ PASS = 'green', FAIL = 'red', SKIP = 'grey'
+ },
+ colormap = {
+ grey = '\27[37m',
+ green = '\27[32m',
+ red = '\27[31m',
+ yellow = '\27[33m'
+ },
+ xml = '',
+ html = '',
+ tests = {},
+ running = {},
+ called = {},
+ passed = 0,
+ failed = 0,
+ skipped = 0,
+ module = module,
+ method = method,
+ index = 1,
+ start = false,
+ }
+ setmetatable(testmodule, self)
+ self.__index = self
+ return testmodule
+ end,
+
+
+ -- @method - TestModule:log()
+ -- @desc - log to console with specific colors, split out to make it easier
+ -- to adjust all console output across the tests
+ -- @param {string} color - color key to use for the log
+ -- @param {string} line - main message to write (LHS)
+ -- @param {string} result - result message to write (RHS)
+ -- @return {nil}
+ log = function(self, color, line, result)
+ if result == nil then result = '' end
+ print(self.colormap[color] .. line .. result)
+ end,
+
+
+ -- @method - TestModule:runTests()
+ -- @desc - starts the running of tests and sets up the list of methods to test
+ -- @param {string} module - module to set for the test suite
+ -- @param {string} method - specific method to test, if nil all methods tested
+ -- @return {nil}
+ runTests = function(self)
+ self.running = {}
+ self.passed = 0
+ self.failed = 0
+ if self.method ~= nil then
+ table.insert(self.running, self.method)
+ else
+ for i,_ in pairs(love.test[self.module]) do
+ table.insert(self.running, i)
+ end
+ table.sort(self.running)
+ end
+ self.index = 1
+ self.start = true
+ self:log('yellow', '\nlove.' .. self.module .. '.testmodule.start')
+ end,
+
+
+ -- @method - TestModule:printResult()
+ -- @desc - prints the result of the module to the console as well as appends
+ -- the XML + HTML for the test to the testsuite output
+ -- @return {nil}
+ printResult = function(self)
+ local finaltime = UtilTimeFormat(self.time)
+ local status = '🔴'
+ if self.failed == 0 then status = '🟢' end
+ -- add md row to main output
+ love.test.mdrows = love.test.mdrows .. '| ' .. status ..
+ ' ' .. self.module ..
+ ' | ' .. tostring(self.passed) ..
+ ' | ' .. tostring(self.failed) ..
+ ' | ' .. tostring(self.skipped) ..
+ ' | ' .. finaltime .. 's |' .. '\n'
+ -- add xml to main output
+ love.test.xml = love.test.xml .. '\t\n' .. self.xml .. '\t\n'
+ -- add html to main output
+ love.test.html = love.test.html .. '' .. status .. ' love.' .. self.module .. '
' ..
+ '- 🟢 ' .. tostring(self.passed) .. ' Tests
' ..
+ '- 🔴 ' .. tostring(self.failed) .. ' Failures
' ..
+ '- 🟡 ' .. tostring(self.skipped) .. ' Skipped
' ..
+ '- ' .. finaltime .. 's
' .. '
' ..
+ ' | Method | Time | Details |
' ..
+ self.html .. '
'
+ -- print module results to console
+ self:log('yellow', 'love.' .. self.module .. '.testmodule.end')
+ local failedcol = '\27[31m'
+ if self.failed == 0 then failedcol = '\27[37m' end
+ self:log('green', tostring(self.passed) .. ' PASSED' .. ' || ' ..
+ failedcol .. tostring(self.failed) .. ' FAILED || \27[37m' ..
+ tostring(self.skipped) .. ' SKIPPED || ' .. finaltime .. 's')
+ self.start = false
+ self.fakequit = false
+ end
+
+
+}
diff --git a/testing/classes/TestSuite.lua b/testing/classes/TestSuite.lua
new file mode 100644
index 000000000..dfaedc64f
--- /dev/null
+++ b/testing/classes/TestSuite.lua
@@ -0,0 +1,183 @@
+TestSuite = {
+
+
+ -- @method - TestSuite:new()
+ -- @desc - creates a new TestSuite object that handles all the tests
+ -- @return {table} - returns the new TestSuite object
+ new = function(self)
+ local test = {
+
+ -- testsuite internals
+ modules = {},
+ module = nil,
+ test = nil,
+ testcanvas = nil,
+ current = 1,
+ output = '',
+ totals = {0, 0, 0},
+ time = 0,
+ xml = '',
+ html = '',
+ mdrows = '',
+ mdfailures = '',
+ delayed = nil,
+ fakequit = false,
+ windowmode = true,
+
+ -- love modules to test
+ audio = {},
+ data = {},
+ event = {},
+ filesystem = {},
+ font = {},
+ graphics = {},
+ image = {},
+ joystick = {},
+ math = {},
+ mouse = {},
+ physics = {},
+ sound = {},
+ system = {},
+ thread = {},
+ timer = {},
+ touch = {},
+ video = {},
+ window = {}
+
+ }
+ setmetatable(test, self)
+ self.__index = self
+ return test
+ end,
+
+
+ -- @method - TestSuite:runSuite()
+ -- @desc - called in love.update, runs through every method or every module
+ -- @param {number} delta - delta from love.update to track time elapsed
+ -- @return {nil}
+ runSuite = function(self, delta)
+
+ -- stagger between tests
+ if self.module ~= nil then
+
+ if self.module.start == true then
+
+ -- work through each test method 1 by 1
+ if self.module.index <= #self.module.running then
+
+ -- run method once
+ if self.module.called[self.module.index] == nil then
+ self.module.called[self.module.index] = true
+ local method = self.module.running[self.module.index]
+ self.test = TestMethod:new(method, self.module)
+ TextRun:set('love.' .. self.module.module .. '.' .. method)
+
+ self.test.co = coroutine.create(function()
+ local ok, chunk, err = pcall(love.test[love.test.module.module][method], love.test.test)
+ if ok == false then
+ love.test.test.passed = false
+ love.test.test.fatal = tostring(chunk) .. tostring(err)
+ end
+ end)
+
+
+ -- once called we have a corouting, so just call resume every frame
+ -- until we have finished
+ else
+
+ -- move onto next yield if any
+ -- pauses can be set with TestMethod:waitFrames(frames)
+ coroutine.resume(self.test.co)
+
+ -- when wait finished (or no yields)
+ if coroutine.status(self.test.co) == 'dead' then
+ -- now we're all done evaluate the test
+ local ok, chunk, err = pcall(self.test.evaluateTest, self.test)
+ if ok == false then
+ self.test.passed = false
+ self.test.fatal = tostring(chunk) .. tostring(err)
+ end
+ -- save having to :release() anything we made in the last test
+ collectgarbage("collect")
+ -- move onto the next test
+ self.module.index = self.module.index + 1
+ end
+
+ end
+
+ -- once all tests have run
+ else
+
+ -- print module results and add to output
+ self.module:printResult()
+
+ -- if we have more modules to go run the next one
+ self.current = self.current + 1
+ if #self.modules >= self.current then
+ self.module = self.modules[self.current]
+ self.module:runTests()
+
+ -- otherwise print the final results and export output
+ else
+ self:printResult()
+ love.event.quit(0)
+ end
+
+ end
+ end
+ end
+
+ end,
+
+
+ -- @method - TestSuite:printResult()
+ -- @desc - prints the result of the whole test suite as well as writes
+ -- the MD, XML + HTML of the testsuite output
+ -- @return {nil}
+ printResult = function(self)
+ local finaltime = UtilTimeFormat(self.time)
+
+ local name, version, vendor, device = love.graphics.getRendererInfo()
+
+ local md = '\n\n### Info\n' ..
+ '**' .. tostring(self.totals[1] + self.totals[2] + self.totals[3]) .. '** tests were completed in **' ..
+ finaltime .. 's** with **' ..
+ tostring(self.totals[1]) .. '** passed, **' ..
+ tostring(self.totals[2]) .. '** failed, and **' ..
+ tostring(self.totals[3]) .. '** skipped\n\n' ..
+ 'Renderer: ' .. name .. ' | ' .. version .. ' | ' .. vendor .. ' | ' .. device .. '\n\n' ..
+ '### Report\n' ..
+ '| Module | Pass | Fail | Skip | Time |\n' ..
+ '| --------------------- | ------ | ------ | ------- | ------ |\n' ..
+ self.mdrows .. '\n### Failures\n' .. self.mdfailures
+
+ local xml = '\n'
+
+ local status = '🔴'
+ if self.totals[2] == 0 then status = '🟢' end
+ local html = '' .. status .. ' love.test
'
+ html = html ..
+ '- 🟢 ' .. tostring(self.totals[1]) .. ' Tests
' ..
+ '- 🔴 ' .. tostring(self.totals[2]) .. ' Failures
' ..
+ '- 🟡 ' .. tostring(self.totals[3]) .. ' Skipped
' ..
+ '- ' .. finaltime .. 's
'
+
+ love.filesystem.write('tempoutput/' .. self.output .. '.xml', xml .. self.xml .. '')
+ love.filesystem.write('tempoutput/' .. self.output .. '.html', html .. self.html .. '
')
+ love.filesystem.write('tempoutput/' .. self.output .. '.md', md)
+
+ self.module:log('grey', '\nFINISHED - ' .. finaltime .. 's\n')
+ local failedcol = '\27[31m'
+ if self.totals[2] == 0 then failedcol = '\27[37m' end
+ self.module:log('green', tostring(self.totals[1]) .. ' PASSED' .. ' || ' .. failedcol .. tostring(self.totals[2]) .. ' FAILED || \27[37m' .. tostring(self.totals[3]) .. ' SKIPPED')
+
+ end
+
+
+}
diff --git a/testing/conf.lua b/testing/conf.lua
new file mode 100644
index 000000000..a5c0de279
--- /dev/null
+++ b/testing/conf.lua
@@ -0,0 +1,24 @@
+function love.conf(t)
+ t.console = true
+ t.window.name = 'love.test'
+ t.window.width = 360
+ t.window.height = 240
+ t.window.resizable = true
+ t.renderers = {"opengl"}
+ t.modules.audio = true
+ t.modules.data = true
+ t.modules.event = true
+ t.modules.filesystem = true
+ t.modules.font = true
+ t.modules.graphics = true
+ t.modules.image = true
+ t.modules.math = true
+ t.modules.objects = true
+ t.modules.physics = true
+ t.modules.sound = true
+ t.modules.system = true
+ t.modules.thread = true
+ t.modules.timer = true
+ t.modules.video = true
+ t.modules.window = true
+end
diff --git a/testing/examples/lovetest_runAllTests.html b/testing/examples/lovetest_runAllTests.html
new file mode 100644
index 000000000..48e480fcc
--- /dev/null
+++ b/testing/examples/lovetest_runAllTests.html
@@ -0,0 +1 @@
+🟢 love.test
- 🟢 287 Tests
- 🔴 0 Failures
- 🟡 14 Skipped
- 14.726s
🟢 love.audio
- 🟢 28 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.894s
| Method | Time | Details |
🟢 | RecordingDevice | 0.398s | |
🟢 | Source | 0.024s | |
🟢 | getActiveEffects | 0.017s | |
🟢 | getActiveSourceCount | 0.018s | |
🟢 | getDistanceModel | 0.018s | |
🟢 | getDopplerScale | 0.019s | |
🟢 | getEffect | 0.019s | |
🟢 | getMaxSceneEffects | 0.016s | |
🟢 | getMaxSourceEffects | 0.017s | |
🟢 | getOrientation | 0.019s | |
🟢 | getPosition | 0.019s | |
🟢 | getRecordingDevices | 0.018s | |
🟢 | getVelocity | 0.019s | |
🟢 | getVolume | 0.018s | |
🟢 | isEffectsSupported | 0.018s | |
🟢 | newQueueableSource | 0.019s | |
🟢 | newSource | 0.019s | |
🟢 | pause | 0.020s | |
🟢 | play | 0.018s | |
🟢 | setDistanceModel | 0.017s | |
🟢 | setDopplerScale | 0.019s | |
🟢 | setEffect | 0.018s | |
🟢 | setMixWithSystem | 0.018s | |
🟢 | setOrientation | 0.018s | |
🟢 | setPosition | 0.017s | |
🟢 | setVelocity | 0.018s | |
🟢 | setVolume | 0.018s | |
🟢 | stop | 0.019s | |
🟢 love.data
- 🟢 12 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.209s
| Method | Time | Details |
🟢 | ByteData | 0.015s | |
🟢 | CompressedData | 0.018s | |
🟢 | compress | 0.019s | |
🟢 | decode | 0.014s | |
🟢 | decompress | 0.018s | |
🟢 | encode | 0.019s | |
🟢 | getPackedSize | 0.019s | |
🟢 | hash | 0.016s | |
🟢 | newByteData | 0.017s | |
🟢 | newDataView | 0.017s | |
🟢 | pack | 0.018s | |
🟢 | unpack | 0.018s | |
🟢 love.event
- 🟢 4 Tests
- 🔴 0 Failures
- 🟡 2 Skipped
- 0.103s
| Method | Time | Details |
🟢 | clear | 0.016s | |
🟢 | poll | 0.018s | |
🟡 | pump | 0.018s | used internally |
🟢 | push | 0.016s | |
🟢 | quit | 0.016s | |
🟡 | wait | 0.019s | used internally |
🟢 love.filesystem
- 🟢 29 Tests
- 🔴 0 Failures
- 🟡 2 Skipped
- 0.550s
| Method | Time | Details |
🟢 | File | 0.019s | |
🟢 | FileData | 0.017s | |
🟢 | append | 0.020s | |
🟢 | areSymlinksEnabled | 0.017s | |
🟢 | createDirectory | 0.017s | |
🟢 | getAppdataDirectory | 0.017s | |
🟢 | getCRequirePath | 0.018s | |
🟢 | getDirectoryItems | 0.018s | |
🟢 | getIdentity | 0.017s | |
🟢 | getInfo | 0.019s | |
🟢 | getRealDirectory | 0.019s | |
🟢 | getRequirePath | 0.018s | |
🟢 | getSaveDirectory | 0.017s | |
🟡 | getSource | 0.017s | used internally |
🟢 | getSourceBaseDirectory | 0.020s | |
🟢 | getUserDirectory | 0.018s | |
🟢 | getWorkingDirectory | 0.018s | |
🟢 | isFused | 0.018s | |
🟢 | lines | 0.020s | |
🟢 | load | 0.018s | |
🟢 | mount | 0.018s | |
🟢 | newFileData | 0.018s | |
🟢 | openFile | 0.018s | |
🟢 | read | 0.017s | |
🟢 | remove | 0.018s | |
🟢 | setCRequirePath | 0.017s | |
🟢 | setIdentity | 0.015s | |
🟢 | setRequirePath | 0.016s | |
🟡 | setSource | 0.016s | used internally |
🟢 | unmount | 0.018s | |
🟢 | write | 0.017s | |
🟢 love.font
- 🟢 7 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.124s
| Method | Time | Details |
🟢 | GlyphData | 0.016s | |
🟢 | Rasterizer | 0.016s | |
🟢 | newBMFontRasterizer | 0.018s | |
🟢 | newGlyphData | 0.018s | |
🟢 | newImageRasterizer | 0.018s | |
🟢 | newRasterizer | 0.018s | |
🟢 | newTrueTypeRasterizer | 0.020s | |
🟢 love.graphics
- 🟢 102 Tests
- 🔴 0 Failures
- 🟡 3 Skipped
- 3.231s
| Method | Time | Details |
🟢 | Canvas | 0.019s | |
🟢 | Font | 0.021s | Expected Actual Expected Actual |
🟢 | Image | 0.020s | Expected Actual |
🟡 | Mesh | 0.018s | test class needs writing |
🟡 | ParticleSystem | 0.017s | test class needs writing |
🟢 | Quad | 0.018s | Expected Actual |
🟢 | Shader | 0.029s | Expected Actual |
🟢 | SpriteBatch | 0.027s | Expected Actual Expected Actual Expected Actual |
🟢 | Text | 0.018s | Expected Actual |
🟢 | Video | 1.040s | Expected Actual |
🟢 | applyTransform | 0.006s | Expected Actual |
🟢 | arc | 0.019s | Expected Actual Expected Actual Expected Actual |
🟢 | captureScreenshot | 0.183s | |
🟢 | circle | 0.020s | Expected Actual |
🟢 | clear | 0.031s | Expected Actual |
🟡 | discard | 0.016s | cant test this worked |
🟢 | draw | 0.017s | Expected Actual |
🟢 | drawInstanced | 0.030s | Expected Actual |
🟢 | drawLayer | 0.025s | Expected Actual |
🟢 | ellipse | 0.017s | Expected Actual |
🟢 | flushBatch | 0.018s | |
🟢 | getBackgroundColor | 0.018s | |
🟢 | getBlendMode | 0.019s | |
🟢 | getCanvas | 0.018s | |
🟢 | getColor | 0.018s | |
🟢 | getColorMask | 0.017s | |
🟢 | getDPIScale | 0.018s | |
🟢 | getDefaultFilter | 0.018s | |
🟢 | getDepthMode | 0.017s | |
🟢 | getDimensions | 0.017s | |
🟢 | getFont | 0.018s | |
🟢 | getFrontFaceWinding | 0.019s | |
🟢 | getHeight | 0.017s | |
🟢 | getLineJoin | 0.017s | |
🟢 | getLineStyle | 0.018s | |
🟢 | getLineWidth | 0.019s | |
🟢 | getMeshCullMode | 0.018s | |
🟢 | getPixelDimensions | 0.018s | |
🟢 | getPixelHeight | 0.018s | |
🟢 | getPixelWidth | 0.018s | |
🟢 | getPointSize | 0.018s | |
🟢 | getRendererInfo | 0.019s | |
🟢 | getScissor | 0.017s | |
🟢 | getShader | 0.017s | |
🟢 | getStackDepth | 0.018s | |
🟢 | getStats | 0.017s | |
🟢 | getStencilMode | 0.019s | |
🟢 | getSupported | 0.016s | |
🟢 | getSystemLimits | 0.017s | |
🟢 | getTextureFormats | 0.019s | |
🟢 | getTextureTypes | 0.018s | |
🟢 | getWidth | 0.018s | |
🟢 | intersectScissor | 0.019s | Expected Actual |
🟢 | inverseTransformPoint | 0.018s | |
🟢 | isActive | 0.019s | |
🟢 | isGammaCorrect | 0.017s | |
🟢 | isWireframe | 0.017s | |
🟢 | line | 0.020s | Expected Actual |
🟢 | newArrayImage | 0.018s | |
🟢 | newCanvas | 0.015s | |
🟢 | newCubeImage | 0.019s | |
🟢 | newFont | 0.019s | |
🟢 | newImage | 0.016s | |
🟢 | newImageFont | 0.017s | |
🟢 | newMesh | 0.017s | |
🟢 | newParticleSystem | 0.018s | |
🟢 | newQuad | 0.018s | |
🟢 | newShader | 0.023s | |
🟢 | newSpriteBatch | 0.016s | |
🟢 | newTextBatch | 0.018s | |
🟢 | newVideo | 0.021s | |
🟢 | newVolumeImage | 0.016s | |
🟢 | origin | 0.018s | Expected Actual |
🟢 | points | 0.020s | Expected Actual |
🟢 | polygon | 0.018s | Expected Actual |
🟢 | pop | 0.019s | Expected Actual |
🟢 | print | 0.019s | Expected Actual |
🟢 | printf | 0.018s | Expected Actual |
🟢 | push | 0.026s | Expected Actual |
🟢 | rectangle | 0.025s | Expected Actual Expected Actual |
🟢 | replaceTransform | 0.019s | Expected Actual |
🟢 | reset | 0.018s | |
🟢 | rotate | 0.030s | Expected Actual |
🟢 | scale | 0.013s | |
🟢 | setBackgroundColor | 0.017s | |
🟢 | setBlendMode | 0.028s | Expected Actual |
🟢 | setCanvas | 0.018s | Expected Actual |
🟢 | setColor | 0.017s | Expected Actual |
🟢 | setColorMask | 0.034s | Expected Actual |
🟢 | setDefaultFilter | 0.014s | |
🟢 | setDepthMode | 0.018s | |
🟢 | setFont | 0.029s | Expected Actual |
🟢 | setFrontFaceWinding | 0.017s | |
🟢 | setLineJoin | 0.019s | Expected Actual |
🟢 | setLineStyle | 0.033s | Expected Actual |
🟢 | setLineWidth | 0.023s | Expected Actual |
🟢 | setMeshCullMode | 0.014s | |
🟢 | setScissor | 0.027s | Expected Actual |
🟢 | setShader | 0.024s | Expected Actual |
🟢 | setStencilMode | 0.019s | Expected Actual |
🟢 | setWireframe | 0.036s | Expected Actual |
🟢 | shear | 0.022s | Expected Actual Expected Actual |
🟢 | transformPoint | 0.017s | |
🟢 | translate | 0.017s | Expected Actual |
🟢 | validateShader | 0.020s | |
🟢 love.image
- 🟢 5 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.098s
| Method | Time | Details |
🟢 | CompressedImageData | 0.017s | |
🟢 | ImageData | 0.036s | |
🟢 | isCompressed | 0.013s | |
🟢 | newCompressedData | 0.016s | |
🟢 | newImageData | 0.016s | |
🟢 love.math
- 🟢 20 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.353s
| Method | Time | Details |
🟢 | BezierCurve | 0.016s | |
🟢 | RandomGenerator | 0.018s | |
🟢 | Transform | 0.016s | |
🟢 | colorFromBytes | 0.017s | |
🟢 | colorToBytes | 0.019s | |
🟢 | gammaToLinear | 0.018s | |
🟢 | getRandomSeed | 0.018s | |
🟢 | getRandomState | 0.017s | |
🟢 | isConvex | 0.018s | |
🟢 | linearToGamma | 0.018s | |
🟢 | newBezierCurve | 0.018s | |
🟢 | newRandomGenerator | 0.017s | |
🟢 | newTransform | 0.018s | |
🟢 | perlinNoise | 0.019s | |
🟢 | random | 0.019s | |
🟢 | randomNormal | 0.018s | |
🟢 | setRandomSeed | 0.017s | |
🟢 | setRandomState | 0.018s | |
🟢 | simplexNoise | 0.017s | |
🟢 | triangulate | 0.017s | |
🟢 love.physics
- 🟢 23 Tests
- 🔴 0 Failures
- 🟡 3 Skipped
- 0.449s
| Method | Time | Details |
🟡 | Body | 0.016s | test class needs writing |
🟡 | Contact | 0.017s | test class needs writing |
🟢 | Joint | 0.018s | |
🟡 | Shape | 0.018s | test class needs writing |
🟢 | World | 0.016s | |
🟢 | getDistance | 0.018s | |
🟢 | getMeter | 0.018s | |
🟢 | newBody | 0.018s | |
🟢 | newChainShape | 0.017s | |
🟢 | newCircleShape | 0.017s | |
🟢 | newDistanceJoint | 0.018s | |
🟢 | newEdgeShape | 0.017s | |
🟢 | newFrictionJoint | 0.017s | |
🟢 | newGearJoint | 0.017s | |
🟢 | newMotorJoint | 0.015s | |
🟢 | newMouseJoint | 0.018s | |
🟢 | newPolygonShape | 0.016s | |
🟢 | newPrismaticJoint | 0.017s | |
🟢 | newPulleyJoint | 0.017s | |
🟢 | newRectangleShape | 0.018s | |
🟢 | newRevoluteJoint | 0.018s | |
🟢 | newRopeJoint | 0.018s | |
🟢 | newWeldJoint | 0.016s | |
🟢 | newWheelJoint | 0.017s | |
🟢 | newWorld | 0.018s | |
🟢 | setMeter | 0.018s | |
🟢 love.sound
- 🟢 4 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.074s
| Method | Time | Details |
🟢 | Decoder | 0.017s | |
🟢 | SoundData | 0.019s | |
🟢 | newDecoder | 0.019s | |
🟢 | newSoundData | 0.019s | |
🟢 love.system
- 🟢 6 Tests
- 🔴 0 Failures
- 🟡 2 Skipped
- 0.144s
| Method | Time | Details |
🟢 | getClipboardText | 0.018s | |
🟢 | getOS | 0.019s | |
🟢 | getPowerInfo | 0.019s | |
🟢 | getProcessorCount | 0.018s | |
🟢 | hasBackgroundMusic | 0.019s | |
🟡 | openURL | 0.018s | cant test this worked |
🟢 | setClipboardText | 0.017s | |
🟡 | vibrate | 0.017s | cant test this worked |
🟢 love.thread
- 🟢 5 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.377s
| Method | Time | Details |
🟢 | Channel | 0.230s | |
🟢 | Thread | 0.099s | |
🟢 | getChannel | 0.016s | |
🟢 | newChannel | 0.016s | |
🟢 | newThread | 0.017s | |
🟢 love.timer
- 🟢 6 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 2.093s
| Method | Time | Details |
🟢 | getAverageDelta | 0.018s | |
🟢 | getDelta | 0.017s | |
🟢 | getFPS | 0.015s | |
🟢 | getTime | 1.019s | |
🟢 | sleep | 1.011s | |
🟢 | step | 0.011s | |
🟢 love.video
- 🟢 2 Tests
- 🔴 0 Failures
- 🟡 0 Skipped
- 0.040s
| Method | Time | Details |
🟢 | VideoStream | 0.019s | |
🟢 | newVideoStream | 0.021s | |
🟢 love.window
- 🟢 34 Tests
- 🔴 0 Failures
- 🟡 2 Skipped
- 5.986s
| Method | Time | Details |
🟢 | close | 0.045s | |
🟢 | fromPixels | 0.002s | |
🟢 | getDPIScale | 0.002s | |
🟢 | getDesktopDimensions | 0.002s | |
🟢 | getDisplayCount | 0.015s | |
🟢 | getDisplayName | 0.017s | |
🟢 | getDisplayOrientation | 0.018s | |
🟢 | getFullscreen | 1.346s | |
🟢 | getFullscreenModes | 0.014s | |
🟢 | getIcon | 0.020s | |
🟢 | getMode | 0.017s | |
🟢 | getPosition | 0.017s | |
🟢 | getSafeArea | 0.017s | |
🟢 | getTitle | 0.018s | |
🟢 | getVSync | 0.017s | |
🟢 | hasFocus | 0.018s | |
🟢 | hasMouseFocus | 0.018s | |
🟢 | isDisplaySleepEnabled | 0.017s | |
🟢 | isMaximized | 0.184s | |
🟢 | isMinimized | 0.728s | |
🟢 | isOpen | 0.049s | |
🟢 | isVisible | 0.024s | |
🟢 | maximize | 0.156s | |
🟢 | minimize | 0.731s | |
🟡 | requestAttention | 0.012s | cant test this worked |
🟢 | restore | 0.864s | |
🟢 | setDisplaySleepEnabled | 0.019s | |
🟢 | setFullscreen | 1.336s | |
🟢 | setIcon | 0.014s | |
🟢 | setMode | 0.022s | |
🟢 | setPosition | 0.180s | |
🟢 | setTitle | 0.019s | |
🟢 | setVSync | 0.016s | |
🟡 | showMessageBox | 0.002s | cant test this worked |
🟢 | toPixels | 0.002s | |
🟢 | updateMode | 0.011s | |
\ No newline at end of file
diff --git a/testing/examples/lovetest_runAllTests.md b/testing/examples/lovetest_runAllTests.md
new file mode 100644
index 000000000..43e37f846
--- /dev/null
+++ b/testing/examples/lovetest_runAllTests.md
@@ -0,0 +1,27 @@
+
+
+### Info
+**301** tests were completed in **14.726s** with **287** passed, **0** failed, and **14** skipped
+
+Renderer: OpenGL | 4.1 Metal - 76.3 | Apple | Apple M1 Max
+
+### Report
+| Module | Pass | Fail | Skip | Time |
+| --------------------- | ------ | ------ | ------- | ------ |
+| 🟢 audio | 28 | 0 | 0 | 0.894s |
+| 🟢 data | 12 | 0 | 0 | 0.209s |
+| 🟢 event | 4 | 0 | 2 | 0.103s |
+| 🟢 filesystem | 29 | 0 | 2 | 0.550s |
+| 🟢 font | 7 | 0 | 0 | 0.124s |
+| 🟢 graphics | 102 | 0 | 3 | 3.231s |
+| 🟢 image | 5 | 0 | 0 | 0.098s |
+| 🟢 math | 20 | 0 | 0 | 0.353s |
+| 🟢 physics | 23 | 0 | 3 | 0.449s |
+| 🟢 sound | 4 | 0 | 0 | 0.074s |
+| 🟢 system | 6 | 0 | 2 | 0.144s |
+| 🟢 thread | 5 | 0 | 0 | 0.377s |
+| 🟢 timer | 6 | 0 | 0 | 2.093s |
+| 🟢 video | 2 | 0 | 0 | 0.040s |
+| 🟢 window | 34 | 0 | 2 | 5.986s |
+
+### Failures
diff --git a/testing/examples/lovetest_runAllTests.xml b/testing/examples/lovetest_runAllTests.xml
new file mode 100644
index 000000000..99b1925c3
--- /dev/null
+++ b/testing/examples/lovetest_runAllTests.xml
@@ -0,0 +1,648 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testing/main.lua b/testing/main.lua
new file mode 100644
index 000000000..2b9ec38da
--- /dev/null
+++ b/testing/main.lua
@@ -0,0 +1,216 @@
+-- & 'c:\Program Files\LOVE\love.exe' ./ --console
+-- /Applications/love_12.app/Contents/MacOS/love ./testing
+
+-- load test objs
+require('classes.TestSuite')
+require('classes.TestModule')
+require('classes.TestMethod')
+
+-- create testsuite obj
+love.test = TestSuite:new()
+
+-- load test scripts if module is active
+if love.audio ~= nil then require('tests.audio') end
+if love.data ~= nil then require('tests.data') end
+if love.event ~= nil then require('tests.event') end
+if love.filesystem ~= nil then require('tests.filesystem') end
+if love.font ~= nil then require('tests.font') end
+if love.graphics ~= nil then require('tests.graphics') end
+if love.image ~= nil then require('tests.image') end
+if love.math ~= nil then require('tests.math') end
+if love.physics ~= nil then require('tests.physics') end
+if love.sound ~= nil then require('tests.sound') end
+if love.system ~= nil then require('tests.system') end
+if love.thread ~= nil then require('tests.thread') end
+if love.timer ~= nil then require('tests.timer') end
+if love.video ~= nil then require('tests.video') end
+if love.window ~= nil then require('tests.window') end
+
+-- love.load
+-- load given arguments and run the test suite
+love.load = function(args)
+
+ -- setup basic img to display
+ if love.window ~= nil then
+ love.window.setMode(360, 240, {
+ fullscreen = false,
+ resizable = true,
+ centered = true
+ })
+ if love.graphics ~= nil then
+ love.graphics.setDefaultFilter("nearest", "nearest")
+ love.graphics.setLineStyle('rough')
+ love.graphics.setLineWidth(1)
+ Logo = {
+ texture = love.graphics.newImage('resources/love.png'),
+ img = nil
+ }
+ Logo.img = love.graphics.newQuad(0, 0, 64, 64, Logo.texture)
+ Font = love.graphics.newFont('resources/font.ttf', 8, 'normal')
+ local txtobj = love.graphics.newTextBatch or love.graphics.newText
+ TextCommand = txtobj(Font, 'Loading...')
+ TextRun = txtobj(Font, '')
+ end
+ end
+
+ -- mount for output later
+ if love.filesystem.mountFullPath then
+ love.filesystem.mountFullPath(love.filesystem.getSource() .. "/output", "tempoutput", "readwrite")
+ end
+
+ -- get all args with any comma lists split out as seperate
+ local arglist = {}
+ for a=1,#args do
+ local splits = UtilStringSplit(args[a], '([^,]+)')
+ for s=1,#splits do
+ table.insert(arglist, splits[s])
+ end
+ end
+
+ -- convert args to the cmd to run, modules, method (if any) and disabled
+ local testcmd = '--runAllTests'
+ local module = ''
+ local method = ''
+ local cmderr = 'Invalid flag used'
+ local modules = {
+ 'audio', 'data', 'event', 'filesystem', 'font', 'graphics',
+ 'image', 'math', 'physics', 'sound', 'system',
+ 'thread', 'timer', 'video', 'window'
+ }
+ GITHUB_RUNNER = false
+ for a=1,#arglist do
+ if testcmd == '--runSpecificMethod' then
+ if module == '' and love[ arglist[a] ] ~= nil then
+ module = arglist[a]
+ table.insert(modules, module)
+ elseif module ~= '' and love[module] ~= nil and method == '' then
+ if love.test[module][arglist[a]] ~= nil then method = arglist[a] end
+ end
+ end
+ if testcmd == '--runSpecificModules' then
+ if love[ arglist[a] ] ~= nil and arglist[a] ~= '--isRunner' then
+ table.insert(modules, arglist[a])
+ end
+ end
+ if arglist[a] == '--runSpecificMethod' then
+ testcmd = arglist[a]
+ modules = {}
+ end
+ if arglist[a] == '--runSpecificModules' then
+ testcmd = arglist[a]
+ modules = {}
+ end
+ if arglist[a] == '--isRunner' then
+ GITHUB_RUNNER = true
+ end
+ end
+
+ -- runSpecificMethod uses the module + method given
+ if testcmd == '--runSpecificMethod' then
+ local testmodule = TestModule:new(module, method)
+ table.insert(love.test.modules, testmodule)
+ if module ~= '' and method ~= '' then
+ love.test.module = testmodule
+ love.test.module:log('grey', '--runSpecificMethod "' .. module .. '" "' .. method .. '"')
+ love.test.output = 'lovetest_runSpecificMethod_' .. module .. '_' .. method
+ else
+ if method == '' then cmderr = 'No valid method specified' end
+ if module == '' then cmderr = 'No valid module specified' end
+ end
+ end
+
+ -- runSpecificModules runs all methods for all the modules given
+ if testcmd == '--runSpecificModules' then
+ local modulelist = {}
+ for m=1,#modules do
+ local testmodule = TestModule:new(modules[m])
+ table.insert(love.test.modules, testmodule)
+ table.insert(modulelist, modules[m])
+ end
+ if #modulelist > 0 then
+ love.test.module = love.test.modules[1]
+ love.test.module:log('grey', '--runSpecificModules "' .. table.concat(modulelist, '" "') .. '"')
+ love.test.output = 'lovetest_runSpecificModules_' .. table.concat(modulelist, '_')
+ else
+ cmderr = 'No modules specified'
+ end
+ end
+
+ -- otherwise default runs all methods for all modules
+ if arglist[1] == nil or arglist[1] == '' or arglist[1] == '--runAllTests' then
+ for m=1,#modules do
+ local testmodule = TestModule:new(modules[m])
+ table.insert(love.test.modules, testmodule)
+ end
+ love.test.module = love.test.modules[1]
+ love.test.module:log('grey', '--runAllTests')
+ love.test.output = 'lovetest_runAllTests'
+ end
+
+ if GITHUB_RUNNER then
+ love.test.module:log('grey', '--isRunner')
+ end
+
+ -- invalid command
+ if love.test.module == nil then
+ print(cmderr)
+ love.event.quit(0)
+ else
+ -- start first module
+ TextCommand:set(testcmd)
+ love.test.module:runTests()
+ end
+
+end
+
+-- love.update
+-- run test suite logic
+love.update = function(delta)
+ love.test:runSuite(delta)
+end
+
+
+-- love.draw
+-- draw a little logo to the screen
+love.draw = function()
+ local lw = (love.graphics.getPixelWidth() - 128) / 2
+ local lh = (love.graphics.getPixelHeight() - 128) / 2
+ love.graphics.draw(Logo.texture, Logo.img, lw, lh, 0, 2, 2)
+ love.graphics.draw(TextCommand, 4, 12, 0, 2, 2)
+ love.graphics.draw(TextRun, 4, 32, 0, 2, 2)
+end
+
+
+-- love.quit
+-- add a hook to allow test modules to fake quit
+love.quit = function()
+ if love.test.module ~= nil and love.test.module.fakequit == true then
+ return true
+ else
+ return false
+ end
+end
+
+
+-- added so bad threads dont fail
+function love.threaderror(thread, errorstr) end
+
+
+-- string split helper
+function UtilStringSplit(str, splitter)
+ local splits = {}
+ for word in string.gmatch(str, splitter) do
+ table.insert(splits, word)
+ end
+ return splits
+end
+
+
+-- string time formatter
+function UtilTimeFormat(seconds)
+ return string.format("%.3f", tostring(seconds))
+end
+
+function UtilDebugLog(a, b, c)
+ if GITHUB_RUNNER == true then print("DEBUG ==> ", a, b, c) end
+end
diff --git a/testing/output/actual/notes.txt b/testing/output/actual/notes.txt
new file mode 100644
index 000000000..2716b1df8
--- /dev/null
+++ b/testing/output/actual/notes.txt
@@ -0,0 +1,2 @@
+# Actual Graphics Output
+The images generated by the tests
\ No newline at end of file
diff --git a/testing/output/expected/love.test.graphics.Canvas-1.png b/testing/output/expected/love.test.graphics.Canvas-1.png
new file mode 100644
index 000000000..3727aa3a0
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Canvas-1.png differ
diff --git a/testing/output/expected/love.test.graphics.Canvas-2.png b/testing/output/expected/love.test.graphics.Canvas-2.png
new file mode 100644
index 000000000..3727aa3a0
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Canvas-2.png differ
diff --git a/testing/output/expected/love.test.graphics.Font-1.png b/testing/output/expected/love.test.graphics.Font-1.png
new file mode 100644
index 000000000..cb2f21a1e
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Font-1.png differ
diff --git a/testing/output/expected/love.test.graphics.Font-2.png b/testing/output/expected/love.test.graphics.Font-2.png
new file mode 100644
index 000000000..d12d68425
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Font-2.png differ
diff --git a/testing/output/expected/love.test.graphics.Image-1.png b/testing/output/expected/love.test.graphics.Image-1.png
new file mode 100644
index 000000000..5f45cf9e7
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Image-1.png differ
diff --git a/testing/output/expected/love.test.graphics.ParticleSystem-1.png b/testing/output/expected/love.test.graphics.ParticleSystem-1.png
new file mode 100644
index 000000000..401a2ef39
Binary files /dev/null and b/testing/output/expected/love.test.graphics.ParticleSystem-1.png differ
diff --git a/testing/output/expected/love.test.graphics.Quad-1.png b/testing/output/expected/love.test.graphics.Quad-1.png
new file mode 100644
index 000000000..22ab17e3e
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Quad-1.png differ
diff --git a/testing/output/expected/love.test.graphics.Shader-1.png b/testing/output/expected/love.test.graphics.Shader-1.png
new file mode 100644
index 000000000..6742f6386
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Shader-1.png differ
diff --git a/testing/output/expected/love.test.graphics.SpriteBatch-1.png b/testing/output/expected/love.test.graphics.SpriteBatch-1.png
new file mode 100644
index 000000000..e3c032481
Binary files /dev/null and b/testing/output/expected/love.test.graphics.SpriteBatch-1.png differ
diff --git a/testing/output/expected/love.test.graphics.SpriteBatch-2.png b/testing/output/expected/love.test.graphics.SpriteBatch-2.png
new file mode 100644
index 000000000..3427e6141
Binary files /dev/null and b/testing/output/expected/love.test.graphics.SpriteBatch-2.png differ
diff --git a/testing/output/expected/love.test.graphics.SpriteBatch-3.png b/testing/output/expected/love.test.graphics.SpriteBatch-3.png
new file mode 100644
index 000000000..36ee516f3
Binary files /dev/null and b/testing/output/expected/love.test.graphics.SpriteBatch-3.png differ
diff --git a/testing/output/expected/love.test.graphics.SpriteBatch-4.png b/testing/output/expected/love.test.graphics.SpriteBatch-4.png
new file mode 100644
index 000000000..d33b0c1ce
Binary files /dev/null and b/testing/output/expected/love.test.graphics.SpriteBatch-4.png differ
diff --git a/testing/output/expected/love.test.graphics.SpriteBatch-5.png b/testing/output/expected/love.test.graphics.SpriteBatch-5.png
new file mode 100644
index 000000000..9171baaec
Binary files /dev/null and b/testing/output/expected/love.test.graphics.SpriteBatch-5.png differ
diff --git a/testing/output/expected/love.test.graphics.Text-1.png b/testing/output/expected/love.test.graphics.Text-1.png
new file mode 100644
index 000000000..4f15348cd
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Text-1.png differ
diff --git a/testing/output/expected/love.test.graphics.Video-1.png b/testing/output/expected/love.test.graphics.Video-1.png
new file mode 100644
index 000000000..ad8b44e9e
Binary files /dev/null and b/testing/output/expected/love.test.graphics.Video-1.png differ
diff --git a/testing/output/expected/love.test.graphics.applyTransform-1.png b/testing/output/expected/love.test.graphics.applyTransform-1.png
new file mode 100644
index 000000000..f80ad8f85
Binary files /dev/null and b/testing/output/expected/love.test.graphics.applyTransform-1.png differ
diff --git a/testing/output/expected/love.test.graphics.arc-1.png b/testing/output/expected/love.test.graphics.arc-1.png
new file mode 100644
index 000000000..f95e65bb3
Binary files /dev/null and b/testing/output/expected/love.test.graphics.arc-1.png differ
diff --git a/testing/output/expected/love.test.graphics.arc-2.png b/testing/output/expected/love.test.graphics.arc-2.png
new file mode 100644
index 000000000..18b6bbe27
Binary files /dev/null and b/testing/output/expected/love.test.graphics.arc-2.png differ
diff --git a/testing/output/expected/love.test.graphics.arc-3.png b/testing/output/expected/love.test.graphics.arc-3.png
new file mode 100644
index 000000000..19354b37a
Binary files /dev/null and b/testing/output/expected/love.test.graphics.arc-3.png differ
diff --git a/testing/output/expected/love.test.graphics.circle-1.png b/testing/output/expected/love.test.graphics.circle-1.png
new file mode 100644
index 000000000..dbc130407
Binary files /dev/null and b/testing/output/expected/love.test.graphics.circle-1.png differ
diff --git a/testing/output/expected/love.test.graphics.clear-1.png b/testing/output/expected/love.test.graphics.clear-1.png
new file mode 100644
index 000000000..315c7a936
Binary files /dev/null and b/testing/output/expected/love.test.graphics.clear-1.png differ
diff --git a/testing/output/expected/love.test.graphics.draw-1.png b/testing/output/expected/love.test.graphics.draw-1.png
new file mode 100644
index 000000000..b2fb6657a
Binary files /dev/null and b/testing/output/expected/love.test.graphics.draw-1.png differ
diff --git a/testing/output/expected/love.test.graphics.drawInstanced-1.png b/testing/output/expected/love.test.graphics.drawInstanced-1.png
new file mode 100644
index 000000000..8211034e0
Binary files /dev/null and b/testing/output/expected/love.test.graphics.drawInstanced-1.png differ
diff --git a/testing/output/expected/love.test.graphics.drawLayer-1.png b/testing/output/expected/love.test.graphics.drawLayer-1.png
new file mode 100644
index 000000000..876bb6f77
Binary files /dev/null and b/testing/output/expected/love.test.graphics.drawLayer-1.png differ
diff --git a/testing/output/expected/love.test.graphics.ellipse-1.png b/testing/output/expected/love.test.graphics.ellipse-1.png
new file mode 100644
index 000000000..3fd46b709
Binary files /dev/null and b/testing/output/expected/love.test.graphics.ellipse-1.png differ
diff --git a/testing/output/expected/love.test.graphics.intersectScissor-1.png b/testing/output/expected/love.test.graphics.intersectScissor-1.png
new file mode 100644
index 000000000..7c40b8c9e
Binary files /dev/null and b/testing/output/expected/love.test.graphics.intersectScissor-1.png differ
diff --git a/testing/output/expected/love.test.graphics.line-1.png b/testing/output/expected/love.test.graphics.line-1.png
new file mode 100644
index 000000000..6fa77e263
Binary files /dev/null and b/testing/output/expected/love.test.graphics.line-1.png differ
diff --git a/testing/output/expected/love.test.graphics.origin-1.png b/testing/output/expected/love.test.graphics.origin-1.png
new file mode 100644
index 000000000..e018bbfbc
Binary files /dev/null and b/testing/output/expected/love.test.graphics.origin-1.png differ
diff --git a/testing/output/expected/love.test.graphics.points-1.png b/testing/output/expected/love.test.graphics.points-1.png
new file mode 100644
index 000000000..93cc0fa41
Binary files /dev/null and b/testing/output/expected/love.test.graphics.points-1.png differ
diff --git a/testing/output/expected/love.test.graphics.polygon-1.png b/testing/output/expected/love.test.graphics.polygon-1.png
new file mode 100644
index 000000000..8107330d3
Binary files /dev/null and b/testing/output/expected/love.test.graphics.polygon-1.png differ
diff --git a/testing/output/expected/love.test.graphics.pop-1.png b/testing/output/expected/love.test.graphics.pop-1.png
new file mode 100644
index 000000000..e018bbfbc
Binary files /dev/null and b/testing/output/expected/love.test.graphics.pop-1.png differ
diff --git a/testing/output/expected/love.test.graphics.print-1.png b/testing/output/expected/love.test.graphics.print-1.png
new file mode 100644
index 000000000..93380064d
Binary files /dev/null and b/testing/output/expected/love.test.graphics.print-1.png differ
diff --git a/testing/output/expected/love.test.graphics.printf-1.png b/testing/output/expected/love.test.graphics.printf-1.png
new file mode 100644
index 000000000..b37a1ac89
Binary files /dev/null and b/testing/output/expected/love.test.graphics.printf-1.png differ
diff --git a/testing/output/expected/love.test.graphics.push-1.png b/testing/output/expected/love.test.graphics.push-1.png
new file mode 100644
index 000000000..58ace03b3
Binary files /dev/null and b/testing/output/expected/love.test.graphics.push-1.png differ
diff --git a/testing/output/expected/love.test.graphics.rectangle-1.png b/testing/output/expected/love.test.graphics.rectangle-1.png
new file mode 100644
index 000000000..1f0e94a4e
Binary files /dev/null and b/testing/output/expected/love.test.graphics.rectangle-1.png differ
diff --git a/testing/output/expected/love.test.graphics.rectangle-2.png b/testing/output/expected/love.test.graphics.rectangle-2.png
new file mode 100644
index 000000000..355c64e43
Binary files /dev/null and b/testing/output/expected/love.test.graphics.rectangle-2.png differ
diff --git a/testing/output/expected/love.test.graphics.replaceTransform-1.png b/testing/output/expected/love.test.graphics.replaceTransform-1.png
new file mode 100644
index 000000000..f80ad8f85
Binary files /dev/null and b/testing/output/expected/love.test.graphics.replaceTransform-1.png differ
diff --git a/testing/output/expected/love.test.graphics.rotate-1.png b/testing/output/expected/love.test.graphics.rotate-1.png
new file mode 100644
index 000000000..7c40b8c9e
Binary files /dev/null and b/testing/output/expected/love.test.graphics.rotate-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setBlendMode-1.png b/testing/output/expected/love.test.graphics.setBlendMode-1.png
new file mode 100644
index 000000000..71ad3bdd9
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setBlendMode-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setCanvas-1.png b/testing/output/expected/love.test.graphics.setCanvas-1.png
new file mode 100644
index 000000000..a97348e50
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setCanvas-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setColor-1.png b/testing/output/expected/love.test.graphics.setColor-1.png
new file mode 100644
index 000000000..3062177c1
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setColor-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setColorMask-1.png b/testing/output/expected/love.test.graphics.setColorMask-1.png
new file mode 100644
index 000000000..315c7a936
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setColorMask-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setFont-1.png b/testing/output/expected/love.test.graphics.setFont-1.png
new file mode 100644
index 000000000..31773fcf9
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setFont-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setLineJoin-1.png b/testing/output/expected/love.test.graphics.setLineJoin-1.png
new file mode 100644
index 000000000..76397bdbe
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setLineJoin-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setLineStyle-1.png b/testing/output/expected/love.test.graphics.setLineStyle-1.png
new file mode 100644
index 000000000..9df1b3817
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setLineStyle-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setLineWidth-1.png b/testing/output/expected/love.test.graphics.setLineWidth-1.png
new file mode 100644
index 000000000..ce30d2cfe
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setLineWidth-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setScissor-1.png b/testing/output/expected/love.test.graphics.setScissor-1.png
new file mode 100644
index 000000000..202f26d04
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setScissor-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setShader-1.png b/testing/output/expected/love.test.graphics.setShader-1.png
new file mode 100644
index 000000000..315c7a936
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setShader-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setStencilMode-1.png b/testing/output/expected/love.test.graphics.setStencilMode-1.png
new file mode 100644
index 000000000..773650483
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setStencilMode-1.png differ
diff --git a/testing/output/expected/love.test.graphics.setWireframe-1.png b/testing/output/expected/love.test.graphics.setWireframe-1.png
new file mode 100644
index 000000000..ceea66531
Binary files /dev/null and b/testing/output/expected/love.test.graphics.setWireframe-1.png differ
diff --git a/testing/output/expected/love.test.graphics.shear-1.png b/testing/output/expected/love.test.graphics.shear-1.png
new file mode 100644
index 000000000..36a779c57
Binary files /dev/null and b/testing/output/expected/love.test.graphics.shear-1.png differ
diff --git a/testing/output/expected/love.test.graphics.shear-2.png b/testing/output/expected/love.test.graphics.shear-2.png
new file mode 100644
index 000000000..3d1873ea5
Binary files /dev/null and b/testing/output/expected/love.test.graphics.shear-2.png differ
diff --git a/testing/output/expected/love.test.graphics.translate-1.png b/testing/output/expected/love.test.graphics.translate-1.png
new file mode 100644
index 000000000..bcb6eb159
Binary files /dev/null and b/testing/output/expected/love.test.graphics.translate-1.png differ
diff --git a/testing/output/expected/notes.txt b/testing/output/expected/notes.txt
new file mode 100644
index 000000000..25b54bba5
--- /dev/null
+++ b/testing/output/expected/notes.txt
@@ -0,0 +1,2 @@
+# Expected Graphics Output
+The images expected by the tests
\ No newline at end of file
diff --git a/testing/output/notes.txt b/testing/output/notes.txt
new file mode 100644
index 000000000..bff4c6375
--- /dev/null
+++ b/testing/output/notes.txt
@@ -0,0 +1,2 @@
+# Testing Output
+Any tests run will output an XML, MD, and HTML file here, assuming the tests are run with readwrite permissions for this repo
\ No newline at end of file
diff --git a/testing/readme.md b/testing/readme.md
new file mode 100644
index 000000000..fb3bcab2b
--- /dev/null
+++ b/testing/readme.md
@@ -0,0 +1,155 @@
+# Lövetest
+Test suite for the [Löve](https://github.com/love2d/love) APIs, based off of [this issue](https://github.com/love2d/love/issues/1745).
+
+Currently written for [Löve 12](https://github.com/love2d/love/tree/12.0-development), which is still in development. As such the test suite may fail if you try to run it with an older version of Löve due to it trying to call methods that don't exist.
+
+While the test suite is part of the main Löve repo, the test suite has it's own repo [here](https://github.com/ellraiser/love-test) so that it can be used with other builds like [love-potion](https://github.com/lovebrew/lovepotion). If you would like to contribute to the test suite please raise a PR on the [love-test](https://github.com/ellraiser/love-test) repo.
+
+---
+
+## Features
+- [x] Simple pass/fail tests written in Lua with minimal setup
+- [x] Ability to run all tests with a simple command
+- [x] Ability to see how many tests are passing/failing
+- [x] Ability to run a subset of tests
+- [x] Ability to easily run an individual test
+- [x] Ability to see all visual results at a glance
+- [x] Compare graphics test output with an expected output
+- [x] Automatic testing that happens after every commit
+- [x] No platform-specific dependencies / scripts
+
+---
+
+## Coverage
+This is the status of all module tests.
+See the **Todo** section for outstanding tasks if you want to contribute!
+| Module | Done | Todo | Skip |
+| ----------------- | ---- | ---- | ---- |
+| 🟢 audio | 28 | 0 | 0 |
+| 🟢 data | 12 | 0 | 0 |
+| 🟢 event | 4 | 0 | 2 |
+| 🟢 filesystem | 29 | 0 | 2 |
+| 🟢 font | 7 | 0 | 0 |
+| 🟢 graphics | 104 | 0 | 1 |
+| 🟢 image | 5 | 0 | 0 |
+| 🟢 math | 20 | 0 | 0 |
+| 🟢 physics | 26 | 0 | 0 |
+| 🟢 sound | 4 | 0 | 0 |
+| 🟢 system | 6 | 0 | 2 |
+| 🟢 thread | 5 | 0 | 0 |
+| 🟢 timer | 6 | 0 | 0 |
+| 🟢 video | 2 | 0 | 0 |
+| 🟢 window | 34 | 0 | 2 |
+
+> The following modules are not covered as we can't really emulate input nicely:
+> `joystick`, `keyboard`, `mouse`, and `touch`
+
+---
+
+## Running Tests
+The testsuite aims to keep things as simple as possible, and just runs all the tests inside Löve to match how they'd be used by developers in-engine.
+To run the tests, download the repo and then run the main.lua as you would a Löve game, i.e:
+
+WINDOWS: `& 'c:\Program Files\LOVE\love.exe' PATH_TO_TESTING_FOLDER/main.lua --console`
+MACOS: `/Applications/love.app/Contents/MacOS/love PATH_TO_TESTING_FOLDER/main.lua`
+LINUX: `./love.AppImage PATH_TO_TESTING_FOLDER/main.lua`
+
+By default all tests will be run for all modules.
+If you want to specify a module/s you can use:
+`--runSpecificModules filesystem,audio`
+If you want to specify only 1 specific method only you can use:
+`--runSpecificMethod filesystem write`
+
+All results will be printed in the console per method as PASS, FAIL, or SKIP with total assertions met on a module level and overall level.
+
+When finished, the following files will be generated in the `/output` directory with a summary of the test results:
+- an `XML` file in the style of [JUnit XML](https://www.ibm.com/docs/en/developer-for-zos/14.1?topic=formats-junit-xml-format)
+- a `HTML` file that shows the report + any visual test results
+- a `Markdown` file you can use with [this github action](https://github.com/ellraiser/love-test-report)
+> An example of all types of output can be found in the `/examples`
+> The visual results of any graphic tests can be found in `/output/actual`
+
+---
+
+## Architecture
+Each method and object has it's own test method written in `/tests` under the matching module name.
+
+When you run the tests, a single TestSuite object is created which handles the progress + totals for all the tests.
+Each module has a TestModule object created, and each test method has a TestMethod object created which keeps track of assertions for that method. You can currently do the following assertions:
+- **assertNotNil**(value)
+- **assertEquals**(expected, actual, label)
+- **assertTrue**(value, label)
+- **assertFalse**(value, label)
+- **assertNotEquals**(expected, actual, label)
+- **assertRange**(actual, min, max, label)
+- **assertMatch**({option1, option2, option3 ...}, actual, label)
+- **assertGreaterEqual**(expected, actual, label)
+- **assertLessEqual**(expected, actual, label)
+- **assertObject**(table)
+- **assertPixels**(imgdata, pixeltable, label)
+- **assertCoords**(expected, actual, label)
+
+Example test method:
+```lua
+-- love.filesystem.read test method
+-- all methods should be put under love.test.MODULE.METHOD, matching the API
+love.test.filesystem.read = function(test)
+ -- setup any data needed then run any asserts using the passed test object
+ local content, size = love.filesystem.read('resources/test.txt')
+ test:assertNotNil(content)
+ test:assertEquals('helloworld', content, 'check content match')
+ test:assertEquals(10, size, 'check size match')
+ content, size = love.filesystem.read('resources/test.txt', 5)
+ test:assertNotNil(content)
+ test:assertEquals('hello', content, 'check content match')
+ test:assertEquals(5, size, 'check size match')
+ -- no need to return anything or cleanup, GCC is called after each method
+end
+```
+
+Each test is run inside it's own coroutine - you can use `test:waitFrames(frames)` to pause the test for a small period if you need to check things that won't happen for a few seconds.
+
+After each test method is ran, the assertions are totalled up, printed, and we move onto the next method! Once all methods in the suite are run a total pass/fail/skip is given for that module and we move onto the next module (if any)
+
+For sanity-checking, if it's currently not covered or it's not possible to test the method we can set the test to be skipped with `test:skipTest(reason)` - this way we still see the method listed in the test output without it affected the pass/fail totals
+
+---
+
+## Todo
+If you would like to contribute to the test suite please raise a PR with the main [love-test](https://github.com/ellraiser/love-test) repo.
+
+The following items are all the things still outstanding, expanding on any existing tests is also very welcome!
+- [ ] check for any 12.0 methods in the changelog not yet covered in the test suite
+- [ ] add BMfont alt. tests for font class tests (Rasterizer + GlyphData)
+- [ ] graphics.isCompressed() should have an example of all compressed files
+- [ ] graphics.Mesh should have some graphical tests ideally to check vertex settings w/ shaders
+- [ ] ability to test loading different combinations of modules if needed
+- [ ] more scenario based tests similar to some of the obj class tests
+- [ ] performance tests? need to discuss what + how
+
+---
+
+## Graphics Tolerance
+By default all graphic tests are run with pixel precision and 0 rgba tolerance.
+
+However there are a couple of methods that on some platforms require some slight tolerance to allow for tiny differences in rendering.
+| Test | OS | Exception | Reason |
+| -------------------------- | --------- | ------------------- | ------ |
+| love.graphics.drawInstanced | Windows | 1rgba tolerance | On Windows there's a couple pixels a tiny bit off, most likely due to complexity of the mesh drawn |
+| love.graphics.setBlendMode | Win/Lin | 1rgba tolerance | Blendmodes have some small varience on some machines |
+
+---
+
+## Runner Exceptions
+The automated tests through Github work for the most part however there are a few exceptions that have to be accounted for due to limitations of the VMs and the graphics emulation used.
+
+These exceptions are either skipped, or handled by using a 1px or 1/255rgba tolerance - when run locally on real hardware, these tests pass fine at the default 0 tolerance.
+You can specify the test suite is being run on a runner by adding the `--isRunner` flag in your workflow file, i.e.:
+`& 'c:\Program Files\LOVE\love.exe' PATH_TO_TESTING_FOLDER/main.lua --console --runAllTests --isRunner`
+| Test | OS | Exception | Reason |
+| -------------------------- | --------- | ------------------- | ------ |
+| love.graphics.points | MacOS | 1px tolerance | Points are offset by 1,1 when drawn |
+| love.graphics.setWireframe | MacOS | 1px tolerance | Wireframes are offset by 1,1 when drawn |
+| love.graphica.arc | MacOS | Skipped | Arc curves are drawn slightly off at really low scale |
+| love.graphics.setLineStyle | Linux | 1rgba tolerance | 'Rough' lines blend differently with the background rgba |
+| love.audio.RecordingDevice | All | Skipped | Recording devices can't be emulated on runners |
diff --git a/testing/resources/alsoft.conf b/testing/resources/alsoft.conf
new file mode 100644
index 000000000..6d1add00f
--- /dev/null
+++ b/testing/resources/alsoft.conf
@@ -0,0 +1,491 @@
+# OpenAL config file.
+#
+# Option blocks may appear multiple times, and duplicated options will take the
+# last value specified. Environment variables may be specified within option
+# values, and are automatically substituted when the config file is loaded.
+# Environment variable names may only contain alpha-numeric characters (a-z,
+# A-Z, 0-9) and underscores (_), and are prefixed with $. For example,
+# specifying "$HOME/file.ext" would typically result in something like
+# "/home/user/file.ext". To specify an actual "$" character, use "$$".
+#
+# Device-specific values may be specified by including the device name in the
+# block name, with "general" replaced by the device name. That is, general
+# options for the device "Name of Device" would be in the [Name of Device]
+# block, while ALSA options would be in the [alsa/Name of Device] block.
+# Options marked as "(global)" are not influenced by the device.
+#
+# The system-wide settings can be put in /etc/openal/alsoft.conf and user-
+# specific override settings in $HOME/.alsoftrc.
+# For Windows, these settings should go into $AppData\alsoft.ini
+#
+# Option and block names are case-senstive. The supplied values are only hints
+# and may not be honored (though generally it'll try to get as close as
+# possible). Note: options that are left unset may default to app- or system-
+# specified values. These are the current available settings:
+
+##
+## General stuff
+##
+[general]
+
+## disable-cpu-exts: (global)
+# Disables use of specialized methods that use specific CPU intrinsics.
+# Certain methods may utilize CPU extensions for improved performance, and
+# this option is useful for preventing some or all of those methods from being
+# used. The available extensions are: sse, sse2, sse3, sse4.1, and neon.
+# Specifying 'all' disables use of all such specialized methods.
+#disable-cpu-exts =
+
+## drivers: (global)
+# Sets the backend driver list order, comma-seperated. Unknown backends and
+# duplicated names are ignored. Unlisted backends won't be considered for use
+# unless the list is ended with a comma (e.g. 'oss,' will try OSS first before
+# other backends, while 'oss' will try OSS only). Backends prepended with -
+# won't be considered for use (e.g. '-oss,' will try all available backends
+# except OSS). An empty list means to try all backends.
+drivers = wave
+
+## channels:
+# Sets the output channel configuration. If left unspecified, one will try to
+# be detected from the system, and defaulting to stereo. The available values
+# are: mono, stereo, quad, surround51, surround51rear, surround61, surround71,
+# ambi1, ambi2, ambi3. Note that the ambi* configurations provide ambisonic
+# channels of the given order (using ACN ordering and SN3D normalization by
+# default), which need to be decoded to play correctly on speakers.
+#channels =
+
+## sample-type:
+# Sets the output sample type. Currently, all mixing is done with 32-bit float
+# and converted to the output sample type as needed. Available values are:
+# int8 - signed 8-bit int
+# uint8 - unsigned 8-bit int
+# int16 - signed 16-bit int
+# uint16 - unsigned 16-bit int
+# int32 - signed 32-bit int
+# uint32 - unsigned 32-bit int
+# float32 - 32-bit float
+#sample-type = float32
+
+## frequency:
+# Sets the output frequency. If left unspecified it will try to detect a
+# default from the system, otherwise it will default to 44100.
+#frequency =
+
+## period_size:
+# Sets the update period size, in frames. This is the number of frames needed
+# for each mixing update. Acceptable values range between 64 and 8192.
+#period_size = 1024
+
+## periods:
+# Sets the number of update periods. Higher values create a larger mix ahead,
+# which helps protect against skips when the CPU is under load, but increases
+# the delay between a sound getting mixed and being heard. Acceptable values
+# range between 2 and 16.
+#periods = 3
+
+## stereo-mode:
+# Specifies if stereo output is treated as being headphones or speakers. With
+# headphones, HRTF or crossfeed filters may be used for better audio quality.
+# Valid settings are auto, speakers, and headphones.
+#stereo-mode = auto
+
+## stereo-encoding:
+# Specifies the encoding method for non-HRTF stereo output. 'panpot' (default)
+# uses standard amplitude panning (aka pair-wise, stereo pair, etc) between
+# -30 and +30 degrees, while 'uhj' creates stereo-compatible two-channel UHJ
+# output, which encodes some surround sound information into stereo output
+# that can be decoded with a surround sound receiver. If crossfeed filters are
+# used, UHJ is disabled.
+#stereo-encoding = panpot
+
+## ambi-format:
+# Specifies the channel order and normalization for the "ambi*" set of channel
+# configurations. Valid settings are: fuma, acn+sn3d, acn+n3d
+#ambi-format = acn+sn3d
+
+## hrtf:
+# Controls HRTF processing. These filters provide better spatialization of
+# sounds while using headphones, but do require a bit more CPU power. The
+# default filters will only work with 44100hz or 48000hz stereo output. While
+# HRTF is used, the cf_level option is ignored. Setting this to auto (default)
+# will allow HRTF to be used when headphones are detected or the app requests
+# it, while setting true or false will forcefully enable or disable HRTF
+# respectively.
+#hrtf = auto
+
+## default-hrtf:
+# Specifies the default HRTF to use. When multiple HRTFs are available, this
+# determines the preferred one to use if none are specifically requested. Note
+# that this is the enumerated HRTF name, not necessarily the filename.
+#default-hrtf =
+
+## hrtf-paths:
+# Specifies a comma-separated list of paths containing HRTF data sets. The
+# format of the files are described in docs/hrtf.txt. The files within the
+# directories must have the .mhr file extension to be recognized. By default,
+# OS-dependent data paths will be used. They will also be used if the list
+# ends with a comma. On Windows this is:
+# $AppData\openal\hrtf
+# And on other systems, it's (in order):
+# $XDG_DATA_HOME/openal/hrtf (defaults to $HOME/.local/share/openal/hrtf)
+# $XDG_DATA_DIRS/openal/hrtf (defaults to /usr/local/share/openal/hrtf and
+# /usr/share/openal/hrtf)
+#hrtf-paths =
+
+## cf_level:
+# Sets the crossfeed level for stereo output. Valid values are:
+# 0 - No crossfeed
+# 1 - Low crossfeed
+# 2 - Middle crossfeed
+# 3 - High crossfeed (virtual speakers are closer to itself)
+# 4 - Low easy crossfeed
+# 5 - Middle easy crossfeed
+# 6 - High easy crossfeed
+# Users of headphones may want to try various settings. Has no effect on non-
+# stereo modes.
+#cf_level = 0
+
+## resampler: (global)
+# Selects the resampler used when mixing sources. Valid values are:
+# point - nearest sample, no interpolation
+# linear - extrapolates samples using a linear slope between samples
+# cubic - extrapolates samples using a Catmull-Rom spline
+# bsinc12 - extrapolates samples using a band-limited Sinc filter (varying
+# between 12 and 24 points, with anti-aliasing)
+# bsinc24 - extrapolates samples using a band-limited Sinc filter (varying
+# between 24 and 48 points, with anti-aliasing)
+#resampler = linear
+
+## rt-prio: (global)
+# Sets real-time priority for the mixing thread. Not all drivers may use this
+# (eg. PortAudio) as they already control the priority of the mixing thread.
+# 0 and negative values will disable it. Note that this may constitute a
+# security risk since a real-time priority thread can indefinitely block
+# normal-priority threads if it fails to wait. As such, the default is
+# disabled.
+#rt-prio = 0
+
+## sources:
+# Sets the maximum number of allocatable sources. Lower values may help for
+# systems with apps that try to play more sounds than the CPU can handle.
+#sources = 256
+
+## slots:
+# Sets the maximum number of Auxiliary Effect Slots an app can create. A slot
+# can use a non-negligible amount of CPU time if an effect is set on it even
+# if no sources are feeding it, so this may help when apps use more than the
+# system can handle.
+#slots = 64
+
+## sends:
+# Limits the number of auxiliary sends allowed per source. Setting this higher
+# than the default has no effect.
+#sends = 16
+
+## front-stablizer:
+# Applies filters to "stablize" front sound imaging. A psychoacoustic method
+# is used to generate a front-center channel signal from the front-left and
+# front-right channels, improving the front response by reducing the combing
+# artifacts and phase errors. Consequently, it will only work with channel
+# configurations that include front-left, front-right, and front-center.
+#front-stablizer = false
+
+## output-limiter:
+# Applies a gain limiter on the final mixed output. This reduces the volume
+# when the output samples would otherwise clamp, avoiding excessive clipping
+# noise.
+#output-limiter = true
+
+## dither:
+# Applies dithering on the final mix, for 8- and 16-bit output by default.
+# This replaces the distortion created by nearest-value quantization with low-
+# level whitenoise.
+#dither = true
+
+## dither-depth:
+# Quantization bit-depth for dithered output. A value of 0 (or less) will
+# match the output sample depth. For int32, uint32, and float32 output, 0 will
+# disable dithering because they're at or beyond the rendered precision. The
+# maximum dither depth is 24.
+#dither-depth = 0
+
+## volume-adjust:
+# A global volume adjustment for source output, expressed in decibels. The
+# value is logarithmic, so +6 will be a scale of (approximately) 2x, +12 will
+# be a scale of 4x, etc. Similarly, -6 will be x1/2, and -12 is about x1/4. A
+# value of 0 means no change.
+#volume-adjust = 0
+
+## excludefx: (global)
+# Sets which effects to exclude, preventing apps from using them. This can
+# help for apps that try to use effects which are too CPU intensive for the
+# system to handle. Available effects are: eaxreverb,reverb,autowah,chorus,
+# compressor,distortion,echo,equalizer,flanger,modulator,dedicated,pshifter,
+# fshifter
+#excludefx =
+
+## default-reverb: (global)
+# A reverb preset that applies by default to all sources on send 0
+# (applications that set their own slots on send 0 will override this).
+# Available presets are: None, Generic, PaddedCell, Room, Bathroom,
+# Livingroom, Stoneroom, Auditorium, ConcertHall, Cave, Arena, Hangar,
+# CarpetedHallway, Hallway, StoneCorridor, Alley, Forest, City, Moutains,
+# Quarry, Plain, ParkingLot, SewerPipe, Underwater, Drugged, Dizzy, Psychotic.
+#default-reverb =
+
+## trap-alc-error: (global)
+# Generates a SIGTRAP signal when an ALC device error is generated, on systems
+# that support it. This helps when debugging, while trying to find the cause
+# of a device error. On Windows, a breakpoint exception is generated.
+#trap-alc-error = false
+
+## trap-al-error: (global)
+# Generates a SIGTRAP signal when an AL context error is generated, on systems
+# that support it. This helps when debugging, while trying to find the cause
+# of a context error. On Windows, a breakpoint exception is generated.
+#trap-al-error = false
+
+##
+## Ambisonic decoder stuff
+##
+[decoder]
+
+## hq-mode:
+# Enables a high-quality ambisonic decoder. This mode is capable of frequency-
+# dependent processing, creating a better reproduction of 3D sound rendering
+# over surround sound speakers. Enabling this also requires specifying decoder
+# configuration files for the appropriate speaker configuration you intend to
+# use (see the quad, surround51, etc options below). Currently, up to third-
+# order decoding is supported.
+hq-mode = false
+
+## distance-comp:
+# Enables compensation for the speakers' relative distances to the listener.
+# This applies the necessary delays and attenuation to make the speakers
+# behave as though they are all equidistant, which is important for proper
+# playback of 3D sound rendering. Requires the proper distances to be
+# specified in the decoder configuration file.
+distance-comp = true
+
+## nfc:
+# Enables near-field control filters. This simulates and compensates for low-
+# frequency effects caused by the curvature of nearby sound-waves, which
+# creates a more realistic perception of sound distance. Note that the effect
+# may be stronger or weaker than intended if the application doesn't use or
+# specify an appropriate unit scale, or if incorrect speaker distances are set
+# in the decoder configuration file. Requires hq-mode to be enabled.
+nfc = true
+
+## nfc-ref-delay
+# Specifies the reference delay value for ambisonic output. When channels is
+# set to one of the ambi* formats, this option enables NFC-HOA output with the
+# specified Reference Delay parameter. The specified value can then be shared
+# with an appropriate NFC-HOA decoder to reproduce correct near-field effects.
+# Keep in mind that despite being designed for higher-order ambisonics, this
+# applies to first-order output all the same. When left unset, normal output
+# is created with no near-field simulation.
+nfc-ref-delay =
+
+## quad:
+# Decoder configuration file for Quadraphonic channel output. See
+# docs/ambdec.txt for a description of the file format.
+quad =
+
+## surround51:
+# Decoder configuration file for 5.1 Surround (Side and Rear) channel output.
+# See docs/ambdec.txt for a description of the file format.
+surround51 =
+
+## surround61:
+# Decoder configuration file for 6.1 Surround channel output. See
+# docs/ambdec.txt for a description of the file format.
+surround61 =
+
+## surround71:
+# Decoder configuration file for 7.1 Surround channel output. See
+# docs/ambdec.txt for a description of the file format. Note: This can be used
+# to enable 3D7.1 with the appropriate configuration and speaker placement,
+# see docs/3D7.1.txt.
+surround71 =
+
+##
+## Reverb effect stuff (includes EAX reverb)
+##
+[reverb]
+
+## boost: (global)
+# A global amplification for reverb output, expressed in decibels. The value
+# is logarithmic, so +6 will be a scale of (approximately) 2x, +12 will be a
+# scale of 4x, etc. Similarly, -6 will be about half, and -12 about 1/4th. A
+# value of 0 means no change.
+#boost = 0
+
+##
+## PulseAudio backend stuff
+##
+[pulse]
+
+## spawn-server: (global)
+# Attempts to autospawn a PulseAudio server whenever needed (initializing the
+# backend, enumerating devices, etc). Setting autospawn to false in Pulse's
+# client.conf will still prevent autospawning even if this is set to true.
+#spawn-server = true
+
+## allow-moves: (global)
+# Allows PulseAudio to move active streams to different devices. Note that the
+# device specifier (seen by applications) will not be updated when this
+# occurs, and neither will the AL device configuration (sample rate, format,
+# etc).
+#allow-moves = false
+
+## fix-rate:
+# Specifies whether to match the playback stream's sample rate to the device's
+# sample rate. Enabling this forces OpenAL Soft to mix sources and effects
+# directly to the actual output rate, avoiding a second resample pass by the
+# PulseAudio server.
+#fix-rate = false
+
+##
+## ALSA backend stuff
+##
+[alsa]
+
+## device: (global)
+# Sets the device name for the default playback device.
+#device = default
+
+## device-prefix: (global)
+# Sets the prefix used by the discovered (non-default) playback devices. This
+# will be appended with "CARD=c,DEV=d", where c is the card id and d is the
+# device index for the requested device name.
+#device-prefix = plughw:
+
+## device-prefix-*: (global)
+# Card- and device-specific prefixes may be used to override the device-prefix
+# option. The option may specify the card id (eg, device-prefix-NVidia), or
+# the card id and device index (eg, device-prefix-NVidia-0). The card id is
+# case-sensitive.
+#device-prefix- =
+
+## capture: (global)
+# Sets the device name for the default capture device.
+#capture = default
+
+## capture-prefix: (global)
+# Sets the prefix used by the discovered (non-default) capture devices. This
+# will be appended with "CARD=c,DEV=d", where c is the card id and d is the
+# device number for the requested device name.
+#capture-prefix = plughw:
+
+## capture-prefix-*: (global)
+# Card- and device-specific prefixes may be used to override the
+# capture-prefix option. The option may specify the card id (eg,
+# capture-prefix-NVidia), or the card id and device index (eg,
+# capture-prefix-NVidia-0). The card id is case-sensitive.
+#capture-prefix- =
+
+## mmap:
+# Sets whether to try using mmap mode (helps reduce latencies and CPU
+# consumption). If mmap isn't available, it will automatically fall back to
+# non-mmap mode. True, yes, on, and non-0 values will attempt to use mmap. 0
+# and anything else will force mmap off.
+#mmap = true
+
+## allow-resampler:
+# Specifies whether to allow ALSA's built-in resampler. Enabling this will
+# allow the playback device to be set to a different sample rate than the
+# actual output, causing ALSA to apply its own resampling pass after OpenAL
+# Soft resamples and mixes the sources and effects for output.
+#allow-resampler = false
+
+##
+## OSS backend stuff
+##
+[oss]
+
+## device: (global)
+# Sets the device name for OSS output.
+#device = /dev/dsp
+
+## capture: (global)
+# Sets the device name for OSS capture.
+#capture = /dev/dsp
+
+##
+## Solaris backend stuff
+##
+[solaris]
+
+## device: (global)
+# Sets the device name for Solaris output.
+#device = /dev/audio
+
+##
+## QSA backend stuff
+##
+[qsa]
+
+##
+## JACK backend stuff
+##
+[jack]
+
+## spawn-server: (global)
+# Attempts to autospawn a JACK server whenever needed (initializing the
+# backend, opening devices, etc).
+#spawn-server = false
+
+## buffer-size:
+# Sets the update buffer size, in samples, that the backend will keep buffered
+# to handle the server's real-time processing requests. This value must be a
+# power of 2, or else it will be rounded up to the next power of 2. If it is
+# less than JACK's buffer update size, it will be clamped. This option may
+# be useful in case the server's update size is too small and doesn't give the
+# mixer time to keep enough audio available for the processing requests.
+#buffer-size = 0
+
+##
+## WASAPI backend stuff
+##
+[wasapi]
+
+##
+## DirectSound backend stuff
+##
+[dsound]
+
+##
+## Windows Multimedia backend stuff
+##
+[winmm]
+
+##
+## PortAudio backend stuff
+##
+[port]
+
+## device: (global)
+# Sets the device index for output. Negative values will use the default as
+# given by PortAudio itself.
+#device = -1
+
+## capture: (global)
+# Sets the device index for capture. Negative values will use the default as
+# given by PortAudio itself.
+#capture = -1
+
+##
+## Wave File Writer stuff
+##
+[wave]
+
+## file: (global)
+# Sets the filename of the wave file to write to. An empty name prevents the
+# backend from opening, even when explicitly requested.
+# THIS WILL OVERWRITE EXISTING FILES WITHOUT QUESTION!
+file = output.wav
+
+## bformat: (global)
+# Creates AMB format files using first-order ambisonics instead of a standard
+# single- or multi-channel .wav file.
+#bformat = false
\ No newline at end of file
diff --git a/testing/resources/click.ogg b/testing/resources/click.ogg
new file mode 100644
index 000000000..49707b3e8
Binary files /dev/null and b/testing/resources/click.ogg differ
diff --git a/testing/resources/clickmono.ogg b/testing/resources/clickmono.ogg
new file mode 100644
index 000000000..d1b567e7d
Binary files /dev/null and b/testing/resources/clickmono.ogg differ
diff --git a/testing/resources/cubemap.png b/testing/resources/cubemap.png
new file mode 100644
index 000000000..2b8b2ed0c
Binary files /dev/null and b/testing/resources/cubemap.png differ
diff --git a/testing/resources/font-letters-ab.png b/testing/resources/font-letters-ab.png
new file mode 100644
index 000000000..3ec8aeca3
Binary files /dev/null and b/testing/resources/font-letters-ab.png differ
diff --git a/testing/resources/font-letters-cd.png b/testing/resources/font-letters-cd.png
new file mode 100644
index 000000000..51190f648
Binary files /dev/null and b/testing/resources/font-letters-cd.png differ
diff --git a/testing/resources/font.bmp b/testing/resources/font.bmp
new file mode 100644
index 000000000..3180d031c
Binary files /dev/null and b/testing/resources/font.bmp differ
diff --git a/testing/resources/font.ttf b/testing/resources/font.ttf
new file mode 100644
index 000000000..0ff4bf7d3
Binary files /dev/null and b/testing/resources/font.ttf differ
diff --git a/testing/resources/love.dxt1 b/testing/resources/love.dxt1
new file mode 100644
index 000000000..88cb9e689
Binary files /dev/null and b/testing/resources/love.dxt1 differ
diff --git a/testing/resources/love.png b/testing/resources/love.png
new file mode 100644
index 000000000..e2612a857
Binary files /dev/null and b/testing/resources/love.png differ
diff --git a/testing/resources/love2.png b/testing/resources/love2.png
new file mode 100644
index 000000000..e2612a857
Binary files /dev/null and b/testing/resources/love2.png differ
diff --git a/testing/resources/love3.png b/testing/resources/love3.png
new file mode 100644
index 000000000..e2612a857
Binary files /dev/null and b/testing/resources/love3.png differ
diff --git a/testing/resources/loveinv.png b/testing/resources/loveinv.png
new file mode 100644
index 000000000..7be943a20
Binary files /dev/null and b/testing/resources/loveinv.png differ
diff --git a/testing/resources/pixel.png b/testing/resources/pixel.png
new file mode 100644
index 000000000..e8b825eae
Binary files /dev/null and b/testing/resources/pixel.png differ
diff --git a/testing/resources/sample.ogv b/testing/resources/sample.ogv
new file mode 100644
index 000000000..f145489c2
Binary files /dev/null and b/testing/resources/sample.ogv differ
diff --git a/testing/resources/test.txt b/testing/resources/test.txt
new file mode 100644
index 000000000..620ffd0fd
--- /dev/null
+++ b/testing/resources/test.txt
@@ -0,0 +1 @@
+helloworld
\ No newline at end of file
diff --git a/testing/resources/test.zip b/testing/resources/test.zip
new file mode 100644
index 000000000..46cfaba97
Binary files /dev/null and b/testing/resources/test.zip differ
diff --git a/testing/tests/audio.lua b/testing/tests/audio.lua
new file mode 100644
index 000000000..3d225c7d6
--- /dev/null
+++ b/testing/tests/audio.lua
@@ -0,0 +1,478 @@
+-- love.audio
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- RecordingDevice (love.audio.getRecordingDevices)
+love.test.audio.RecordingDevice = function(test)
+
+ -- skip recording device on runners, they cant emulate it
+ if GITHUB_RUNNER == true then
+ return test:skipTest('cant emulate recording devices in CI')
+ end
+
+ -- check devices first
+ local devices = love.audio.getRecordingDevices()
+ if #devices == 0 then
+ return test:skipTest('cant test this works: no recording devices found')
+ end
+
+ -- check object created and basics
+ local device = devices[1]
+ test:assertObject(device)
+ test:assertMatch({1, 2}, device:getChannelCount(), 'check channel count is 1 or 2')
+ test:assertNotEquals(nil, device:getName(), 'check has name')
+
+ -- check initial data is empty as we haven't recorded anything yet
+ test:assertNotNil(device:getBitDepth())
+ test:assertEquals(nil, device:getData(), 'check initial data empty')
+ test:assertEquals(0, device:getSampleCount(), 'check initial sample empty')
+ test:assertNotNil(device:getSampleRate())
+ test:assertFalse(device:isRecording(), 'check not recording')
+
+ -- start recording for a short time
+ local startrecording = device:start(32000, 4000, 16, 1)
+ test:waitFrames(10)
+ test:assertTrue(startrecording, 'check recording started')
+ test:assertTrue(device:isRecording(), 'check now recording')
+ test:assertEquals(4000, device:getSampleRate(), 'check sample rate set')
+ test:assertEquals(16, device:getBitDepth(), 'check bit depth set')
+ test:assertEquals(1, device:getChannelCount(), 'check channel count set')
+ local recording = device:stop()
+ test:waitFrames(10)
+
+ -- after recording
+ test:assertFalse(device:isRecording(), 'check not recording')
+ test:assertEquals(nil, device:getData(), 'using stop should clear buffer')
+ test:assertObject(recording)
+
+end
+
+
+-- Source (love.audio.newSource)
+love.test.audio.Source = function(test)
+
+ -- create stereo source
+ local stereo = love.audio.newSource('resources/click.ogg', 'static')
+ test:assertObject(stereo)
+
+ -- check stereo props
+ test:assertEquals(2, stereo:getChannelCount(), 'check stereo src')
+ test:assertRange(stereo:getDuration("seconds"), 0, 0.1, 'check stereo seconds')
+ test:assertNotNil(stereo:getFreeBufferCount())
+ test:assertEquals('static', stereo:getType(), 'check stereo type')
+
+ -- check cloning a stereo
+ local clone = stereo:clone()
+ test:assertEquals(2, clone:getChannelCount(), 'check clone stereo src')
+ test:assertRange(clone:getDuration("seconds"), 0, 0.1, 'check clone stereo seconds')
+ test:assertNotNil(clone:getFreeBufferCount())
+ test:assertEquals('static', clone:getType(), 'check cloned stereo type')
+
+ -- mess with stereo playing
+ test:assertFalse(stereo:isPlaying(), 'check not playing')
+ stereo:setLooping(true)
+ stereo:play()
+ test:assertTrue(stereo:isPlaying(), 'check now playing')
+ test:assertTrue(stereo:isLooping(), 'check now playing')
+ stereo:pause()
+ stereo:seek(0.01, 'seconds')
+ test:assertEquals(0.01, stereo:tell('seconds'), 'check seek/tell')
+ stereo:stop()
+ test:assertFalse(stereo:isPlaying(), 'check stopped playing')
+
+ -- check volume limits
+ stereo:setVolumeLimits(0.1, 0.5)
+ local min, max = stereo:getVolumeLimits()
+ test:assertRange(min, 0.1, 0.2, 'check min limit')
+ test:assertRange(max, 0.5, 0.6, 'check max limit')
+
+ -- check setting volume
+ stereo:setVolume(1)
+ test:assertEquals(1, stereo:getVolume(), 'check set volume')
+ stereo:setVolume(0)
+ test:assertEquals(0, stereo:getVolume(), 'check set volume')
+
+ -- change some get/set props that can apply to stereo
+ stereo:setPitch(2)
+ test:assertEquals(2, stereo:getPitch(), 'check pitch change')
+
+ -- create mono source
+ local mono = love.audio.newSource('resources/clickmono.ogg', 'stream')
+ test:assertObject(mono)
+ test:assertEquals(1, mono:getChannelCount(), 'check mono src')
+ test:assertEquals(2927, mono:getDuration("samples"), 'check mono seconds')
+ test:assertEquals('stream', mono:getType(), 'check mono type')
+
+ -- air absorption
+ test:assertEquals(0, mono:getAirAbsorption(), 'get air absorption')
+ mono:setAirAbsorption(1)
+ test:assertEquals(1, mono:getAirAbsorption(), 'set air absorption')
+
+ -- cone
+ mono:setCone(0, 90*(math.pi/180), 1)
+ local ia, oa, ov = mono:getCone()
+ test:assertEquals(0, ia, 'check cone ia')
+ test:assertEquals(math.floor(9000*(math.pi/180)), math.floor(oa*100), 'check cone oa')
+ test:assertEquals(1, ov, 'check cone ov')
+
+ -- direction
+ mono:setDirection(3, 1, -1)
+ local x, y, z = mono:getDirection()
+ test:assertEquals(3, x, 'check direction x')
+ test:assertEquals(1, y, 'check direction y')
+ test:assertEquals(-1, z, 'check direction z')
+
+ -- relative
+ mono:setRelative(true)
+ test:assertTrue(mono:isRelative(), 'check set relative')
+
+ -- position
+ mono:setPosition(1, 2, 3)
+ x, y, z = mono:getPosition()
+ test:assertEquals(x, 1, 'check pos x')
+ test:assertEquals(y, 2, 'check pos y')
+ test:assertEquals(z, 3, 'check pos z')
+
+ -- velocity
+ mono:setVelocity(1, 3, 4)
+ x, y, z = mono:getVelocity()
+ test:assertEquals(x, 1, 'check velocity x')
+ test:assertEquals(y, 3, 'check velocity x')
+ test:assertEquals(z, 4, 'check velocity x')
+
+ -- rolloff
+ mono:setRolloff(1)
+ test:assertEquals(1, mono:getRolloff(), 'check rolloff set')
+
+ -- create queue source
+ local queue = love.audio.newQueueableSource(44100, 16, 1, 3)
+ local sdata = love.sound.newSoundData(1024, 44100, 16, 1)
+ test:assertObject(queue)
+ local run = queue:queue(sdata)
+ test:assertTrue(run, 'check queued sound')
+ queue:stop()
+
+ -- check making a filer
+ local setfilter = stereo:setFilter({
+ type = 'lowpass',
+ volume = 0.5,
+ highgain = 0.3
+ })
+ test:assertTrue(setfilter, 'check filter applied')
+ local filter = stereo:getFilter()
+ test:assertEquals('lowpass', filter.type, 'check filter type')
+ test:assertEquals(0.5, filter.volume, 'check filter volume')
+ test:assertRange(filter.highgain, 0.3, 0.4, 'check filter highgain')
+ test:assertEquals(nil, filter.lowgain, 'check filter lowgain')
+
+ -- add an effect
+ local effsource = love.audio.newSource('resources/click.ogg', 'static')
+ love.audio.setEffect('testeffect', {
+ type = 'flanger',
+ volume = 10
+ })
+ local seteffect, err = effsource:setEffect('testeffect', {
+ type = 'highpass',
+ volume = 0.3,
+ lowgain = 0.1
+ })
+
+ -- both these fail on 12 using stereo or mono, no err
+ test:assertTrue(seteffect, 'check effect was applied')
+ local filtersettings = effsource:getEffect('effectthatdoesntexist', {})
+ test:assertNotNil(filtersettings)
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.audio.getActiveEffects
+love.test.audio.getActiveEffects = function(test)
+ -- check we get a value
+ test:assertNotNil(love.audio.getActiveEffects())
+ -- check setting an effect active
+ love.audio.setEffect('testeffect', {
+ type = 'chorus',
+ volume = 10
+ })
+ test:assertEquals(1, #love.audio.getActiveEffects(), 'check 1 effect running')
+ test:assertEquals('testeffect', love.audio.getActiveEffects()[1], 'check effect details')
+end
+
+
+-- love.audio.getActiveSourceCount
+love.test.audio.getActiveSourceCount = function(test)
+ -- check we get a value
+ test:assertNotNil(love.audio.getActiveSourceCount())
+ -- check source isn't active by default
+ local testsource = love.audio.newSource('resources/click.ogg', 'static')
+ test:assertEquals(0, love.audio.getActiveSourceCount(), 'check not active')
+ -- check playing a source marks it as active
+ love.audio.play(testsource)
+ test:assertEquals(1, love.audio.getActiveSourceCount(), 'check now active')
+ love.audio.pause()
+end
+
+
+-- love.audio.getDistanceModel
+love.test.audio.getDistanceModel = function(test)
+ -- check we get a value
+ test:assertNotNil(love.audio.getDistanceModel())
+ -- check default value from documentation
+ test:assertEquals('inverseclamped', love.audio.getDistanceModel(), 'check default value')
+ -- check get correct value after setting
+ love.audio.setDistanceModel('inverse')
+ test:assertEquals('inverse', love.audio.getDistanceModel(), 'check setting model')
+end
+
+
+-- love.audio.getDopplerScale
+love.test.audio.getDopplerScale = function(test)
+ -- check default value
+ test:assertEquals(1, love.audio.getDopplerScale(), 'check default 1')
+ -- check correct value after setting to 0
+ love.audio.setDopplerScale(0)
+ test:assertEquals(0, love.audio.getDopplerScale(), 'check setting to 0')
+ love.audio.setDopplerScale(1)
+end
+
+
+-- love.audio.getEffect
+love.test.audio.getEffect = function(test)
+ -- check getting a non-existent effect
+ test:assertEquals(nil, love.audio.getEffect('madeupname'), 'check wrong name')
+ -- check getting a valid effect
+ love.audio.setEffect('testeffect', {
+ type = 'chorus',
+ volume = 10
+ })
+ test:assertNotNil(love.audio.getEffect('testeffect'))
+ -- check effect values match creation values
+ test:assertEquals('chorus', love.audio.getEffect('testeffect').type, 'check effect type')
+ test:assertEquals(10, love.audio.getEffect('testeffect').volume, 'check effect volume')
+end
+
+
+-- love.audio.getMaxSceneEffects
+-- @NOTE feel like this is platform specific number so best we can do is a nil?
+love.test.audio.getMaxSceneEffects = function(test)
+ test:assertNotNil(love.audio.getMaxSceneEffects())
+end
+
+
+-- love.audio.getMaxSourceEffects
+-- @NOTE feel like this is platform specific number so best we can do is a nil?
+love.test.audio.getMaxSourceEffects = function(test)
+ test:assertNotNil(love.audio.getMaxSourceEffects())
+end
+
+
+-- love.audio.getOrientation
+-- @NOTE is there an expected default listener pos?
+love.test.audio.getOrientation = function(test)
+ -- checking getting values matches what was set
+ love.audio.setOrientation(1, 2, 3, 4, 5, 6)
+ local fx, fy, fz, ux, uy, uz = love.audio.getOrientation()
+ test:assertEquals(1, fx, 'check fx orientation')
+ test:assertEquals(2, fy, 'check fy orientation')
+ test:assertEquals(3, fz, 'check fz orientation')
+ test:assertEquals(4, ux, 'check ux orientation')
+ test:assertEquals(5, uy, 'check uy orientation')
+ test:assertEquals(6, uz, 'check uz orientation')
+end
+
+
+-- love.audio.getPosition
+-- @NOTE is there an expected default listener pos?
+love.test.audio.getPosition = function(test)
+ -- check getting values matches what was set
+ love.audio.setPosition(1, 2, 3)
+ local x, y, z = love.audio.getPosition()
+ test:assertEquals(1, x, 'check x position')
+ test:assertEquals(2, y, 'check y position')
+ test:assertEquals(3, z, 'check z position')
+end
+
+
+-- love.audio.getRecordingDevices
+-- @NOTE hardware dependent so best can do is not nil check
+love.test.audio.getRecordingDevices = function(test)
+ test:assertNotNil(love.audio.getRecordingDevices())
+end
+
+
+-- love.audio.getVelocity
+love.test.audio.getVelocity = function(test)
+ -- check getting values matches what was set
+ love.audio.setVelocity(1, 2, 3)
+ local x, y, z = love.audio.getVelocity()
+ test:assertEquals(1, x, 'check x velocity')
+ test:assertEquals(2, y, 'check y velocity')
+ test:assertEquals(3, z, 'check z velocity')
+end
+
+
+-- love.audio.getVolume
+love.test.audio.getVolume = function(test)
+ -- check getting values matches what was set
+ love.audio.setVolume(0.5)
+ test:assertEquals(0.5, love.audio.getVolume(), 'check matches set')
+end
+
+
+-- love.audio.isEffectsSupported
+love.test.audio.isEffectsSupported = function(test)
+ test:assertNotNil(love.audio.isEffectsSupported())
+end
+
+
+-- love.audio.newQueueableSource
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.audio.newQueueableSource = function(test)
+ test:assertObject(love.audio.newQueueableSource(32, 8, 1, 8))
+end
+
+
+-- love.audio.newSource
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.audio.newSource = function(test)
+ test:assertObject(love.audio.newSource('resources/click.ogg', 'static'))
+ test:assertObject(love.audio.newSource('resources/click.ogg', 'stream'))
+end
+
+
+-- love.audio.pause
+love.test.audio.pause = function(test)
+ -- check nothing paused (as should be nothing playing)
+ local nopauses = love.audio.pause()
+ test:assertEquals(0, #nopauses, 'check nothing paused')
+ -- check 1 source paused after playing/pausing 1
+ local source = love.audio.newSource('resources/click.ogg', 'static')
+ love.audio.play(source)
+ local onepause = love.audio.pause()
+ test:assertEquals(1, #onepause, 'check 1 paused')
+end
+
+
+-- love.audio.play
+love.test.audio.play = function(test)
+ -- check playing source is detected
+ local source = love.audio.newSource('resources/click.ogg', 'static')
+ love.audio.play(source)
+ test:assertTrue(source:isPlaying(), 'check something playing')
+ love.audio.pause()
+end
+
+
+-- love.audio.setDistanceModel
+love.test.audio.setDistanceModel = function(test)
+ -- check setting each of the distance models is accepted and val returned
+ local distancemodel = {
+ 'none', 'inverse', 'inverseclamped', 'linear', 'linearclamped',
+ 'exponent', 'exponentclamped'
+ }
+ for d=1,#distancemodel do
+ love.audio.setDistanceModel(distancemodel[d])
+ test:assertEquals(distancemodel[d], love.audio.getDistanceModel(),
+ 'check model set to ' .. distancemodel[d])
+ end
+end
+
+
+-- love.audio.setDopplerScale
+love.test.audio.setDopplerScale = function(test)
+ -- check setting value is returned properly
+ love.audio.setDopplerScale(0)
+ test:assertEquals(0, love.audio.getDopplerScale(), 'check set to 0')
+ love.audio.setDopplerScale(1)
+ test:assertEquals(1, love.audio.getDopplerScale(), 'check set to 1')
+end
+
+
+-- love.audio.setEffect
+love.test.audio.setEffect = function(test)
+ -- check effect is set correctly
+ local effect = love.audio.setEffect('testeffect', {
+ type = 'chorus',
+ volume = 10
+ })
+ test:assertTrue(effect, 'check effect created')
+ -- check values set match
+ local settings = love.audio.getEffect('testeffect')
+ test:assertEquals('chorus', settings.type, 'check effect type')
+ test:assertEquals(10, settings.volume, 'check effect volume')
+end
+
+
+-- love.audio.setMixWithSystem
+love.test.audio.setMixWithSystem = function(test)
+ test:assertNotNil(love.audio.setMixWithSystem(true))
+end
+
+
+-- love.audio.setOrientation
+love.test.audio.setOrientation = function(test)
+ -- check setting orientation vals are returned
+ love.audio.setOrientation(1, 2, 3, 4, 5, 6)
+ local fx, fy, fz, ux, uy, uz = love.audio.getOrientation()
+ test:assertEquals(1, fx, 'check fx orientation')
+ test:assertEquals(2, fy, 'check fy orientation')
+ test:assertEquals(3, fz, 'check fz orientation')
+ test:assertEquals(4, ux, 'check ux orientation')
+ test:assertEquals(5, uy, 'check uy orientation')
+ test:assertEquals(6, uz, 'check uz orientation')
+end
+
+
+-- love.audio.setPosition
+love.test.audio.setPosition = function(test)
+ -- check setting position vals are returned
+ love.audio.setPosition(1, 2, 3)
+ local x, y, z = love.audio.getPosition()
+ test:assertEquals(1, x, 'check x position')
+ test:assertEquals(2, y, 'check y position')
+ test:assertEquals(3, z, 'check z position')
+end
+
+
+-- love.audio.setVelocity
+love.test.audio.setVelocity = function(test)
+ -- check setting velocity vals are returned
+ love.audio.setVelocity(1, 2, 3)
+ local x, y, z = love.audio.getVelocity()
+ test:assertEquals(1, x, 'check x velocity')
+ test:assertEquals(2, y, 'check y velocity')
+ test:assertEquals(3, z, 'check z velocity')
+end
+
+
+-- love.audio.setVolume
+love.test.audio.setVolume = function(test)
+ -- check setting volume works
+ love.audio.setVolume(0.5)
+ test:assertEquals(0.5, love.audio.getVolume(), 'check set to 0.5')
+end
+
+
+-- love.audio.stop
+love.test.audio.stop = function(test)
+ -- check source is playing first
+ local source = love.audio.newSource('resources/click.ogg', 'static')
+ love.audio.play(source)
+ test:assertTrue(source:isPlaying(), 'check is playing')
+ -- check source is then stopped
+ love.audio.stop()
+ test:assertFalse(source:isPlaying(), 'check stopped playing')
+end
diff --git a/testing/tests/data.lua b/testing/tests/data.lua
new file mode 100644
index 000000000..0e21a7713
--- /dev/null
+++ b/testing/tests/data.lua
@@ -0,0 +1,251 @@
+-- love.data
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- ByteData (love.data.newByteData)
+love.test.data.ByteData = function(test)
+
+ -- create new obj
+ local data = love.data.newByteData('helloworld')
+ test:assertObject(data)
+
+ -- check properties match expected
+ test:assertEquals('helloworld', data:getString(), 'check data string')
+ test:assertEquals(10, data:getSize(), 'check data size')
+
+ -- check cloning the bytedata
+ local cloneddata = data:clone()
+ test:assertObject(cloneddata)
+ test:assertEquals('helloworld', cloneddata:getString(), 'check cloned data')
+ test:assertEquals(10, cloneddata:getSize(), 'check cloned size')
+
+ -- check pointer access if allowed
+ if data:getFFIPointer() ~= nil and ffi ~= nil then
+ local pointer = data:getFFIPointer()
+ local ptr = ffi.cast('uint8_t*', pointer)
+ local byte5 = ptr[4]
+ test:assertEquals('o', byte5)
+ end
+
+end
+
+
+-- CompressedData (love.data.compress)
+love.test.data.CompressedData = function(test)
+
+ -- create new compressed data
+ local cdata = love.data.compress('data', 'zlib', 'helloworld', -1)
+ test:assertObject(cdata)
+ test:assertEquals('zlib', cdata:getFormat(), 'check format used')
+
+ -- check properties match expected
+ test:assertEquals(18, cdata:getSize())
+ test:assertEquals('helloworld', love.data.decompress('data', cdata):getString())
+
+ -- check cloning the data
+ local clonedcdata = cdata:clone()
+ test:assertObject(clonedcdata)
+ test:assertEquals('zlib', clonedcdata:getFormat())
+ test:assertEquals(18, clonedcdata:getSize())
+ test:assertEquals('helloworld', love.data.decompress('data', clonedcdata):getString())
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.data.compress
+love.test.data.compress = function(test)
+ -- here just testing each combo 'works' - in decompress's test method
+ -- we actually check the compress + decompress give the right value back
+ local compressions = {
+ { love.data.compress('string', 'lz4', 'helloworld', -1), 'string'},
+ { love.data.compress('string', 'lz4', 'helloworld', 0), 'string'},
+ { love.data.compress('string', 'lz4', 'helloworld', 9), 'string'},
+ { love.data.compress('string', 'zlib', 'helloworld', -1), 'string'},
+ { love.data.compress('string', 'zlib', 'helloworld', 0), 'string'},
+ { love.data.compress('string', 'zlib', 'helloworld', 9), 'string'},
+ { love.data.compress('string', 'gzip', 'helloworld', -1), 'string'},
+ { love.data.compress('string', 'gzip', 'helloworld', 0), 'string'},
+ { love.data.compress('string', 'gzip', 'helloworld', 9), 'string'},
+ { love.data.compress('data', 'lz4', 'helloworld', -1), 'userdata'},
+ { love.data.compress('data', 'lz4', 'helloworld', 0), 'userdata'},
+ { love.data.compress('data', 'lz4', 'helloworld', 9), 'userdata'},
+ { love.data.compress('data', 'zlib', 'helloworld', -1), 'userdata'},
+ { love.data.compress('data', 'zlib', 'helloworld', 0), 'userdata'},
+ { love.data.compress('data', 'zlib', 'helloworld', 9), 'userdata'},
+ { love.data.compress('data', 'gzip', 'helloworld', -1), 'userdata'},
+ { love.data.compress('data', 'gzip', 'helloworld', 0), 'userdata'},
+ { love.data.compress('data', 'gzip', 'helloworld', 9), 'userdata'}
+ }
+ for c=1,#compressions do
+ test:assertNotNil(compressions[c][1])
+ -- sense check return type and make sure bytedata returns are an object
+ test:assertEquals(compressions[c][2], type(compressions[c][1]), 'check is userdata')
+ if compressions[c][2] == 'userdata' then
+ test:assertNotEquals(nil, compressions[c][1]:type(), 'check has :type()')
+ end
+ end
+end
+
+
+-- love.data.decode
+love.test.data.decode = function(test)
+ -- setup encoded strings
+ local str1 = love.data.encode('string', 'base64', 'helloworld', 0)
+ local str2 = love.data.encode('string', 'hex', 'helloworld')
+ local str3 = love.data.encode('data', 'base64', 'helloworld', 0)
+ local str4 = love.data.encode('data', 'hex', 'helloworld')
+ -- check value matches expected when decoded back
+ test:assertEquals('helloworld', love.data.decode('string', 'base64', str1), 'check string base64 decode')
+ test:assertEquals('helloworld', love.data.decode('string', 'hex', str2), 'check string hex decode')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decode('data', 'base64', str3):getString(), 'check data base64 decode')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decode('data', 'hex', str4):getString(), 'check data hex decode')
+end
+
+
+-- love.data.decompress
+love.test.data.decompress = function(test)
+ -- setup compressed data for each combination
+ local str1 = love.data.compress('string', 'lz4', 'helloworld', -1)
+ local str2 = love.data.compress('string', 'lz4', 'helloworld', 0)
+ local str3 = love.data.compress('string', 'lz4', 'helloworld', 9)
+ local str4 = love.data.compress('string', 'zlib', 'helloworld', -1)
+ local str5 = love.data.compress('string', 'zlib', 'helloworld', 0)
+ local str6 = love.data.compress('string', 'zlib', 'helloworld', 9)
+ local str7 = love.data.compress('string', 'gzip', 'helloworld', -1)
+ local str8 = love.data.compress('string', 'gzip', 'helloworld', 0)
+ local str9 = love.data.compress('string', 'gzip', 'helloworld', 9)
+ local str10 = love.data.compress('data', 'lz4', 'helloworld', -1)
+ local str11 = love.data.compress('data', 'lz4', 'helloworld', 0)
+ local str12 = love.data.compress('data', 'lz4', 'helloworld', 9)
+ local str13 = love.data.compress('data', 'zlib', 'helloworld', -1)
+ local str14 = love.data.compress('data', 'zlib', 'helloworld', 0)
+ local str15 = love.data.compress('data', 'zlib', 'helloworld', 9)
+ local str16 = love.data.compress('data', 'gzip', 'helloworld', -1)
+ local str17 = love.data.compress('data', 'gzip', 'helloworld', 0)
+ local str18 = love.data.compress('data', 'gzip', 'helloworld', 9)
+ -- check decompressed value matches whats expected
+ test:assertEquals('helloworld', love.data.decompress('string', 'lz4', str1), 'check string lz4 decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'lz4', str2), 'check string lz4 decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'lz4', str3), 'check string lz4 decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'zlib', str4), 'check string zlib decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'zlib', str5), 'check string zlib decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'zlib', str6), 'check string zlib decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'gzip', str7), 'check string glib decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'gzip', str8), 'check string glib decompress')
+ test:assertEquals('helloworld', love.data.decompress('string', 'gzip', str9), 'check string glib decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'lz4', str10):getString(), 'check data lz4 decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'lz4', str11):getString(), 'check data lz4 decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'lz4', str12):getString(), 'check data lz4 decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'zlib', str13):getString(), 'check data zlib decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'zlib', str14):getString(), 'check data zlib decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'zlib', str15):getString(), 'check data zlib decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'gzip', str16):getString(), 'check data glib decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'gzip', str17):getString(), 'check data glib decompress')
+ test:assertEquals(love.data.newByteData('helloworld'):getString(), love.data.decompress('data', 'gzip', str18):getString(), 'check data glib decompress')
+end
+
+
+-- love.data.encode
+love.test.data.encode = function(test)
+ -- here just testing each combo 'works' - in decode's test method
+ -- we actually check the encode + decode give the right value back
+ local encodes = {
+ { love.data.encode('string', 'base64', 'helloworld', 0), 'string'},
+ { love.data.encode('string', 'base64', 'helloworld', 2), 'string'},
+ { love.data.encode('string', 'hex', 'helloworld'), 'string'},
+ { love.data.encode('data', 'base64', 'helloworld', 0), 'userdata'},
+ { love.data.encode('data', 'base64', 'helloworld', 2), 'userdata'},
+ { love.data.encode('data', 'hex', 'helloworld'), 'userdata'}
+ }
+ for e=1,#encodes do
+ test:assertNotNil(encodes[e][1])
+ -- sense check return type and make sure bytedata returns are an object
+ test:assertEquals(encodes[e][2], type(encodes[e][1]), 'check is usedata')
+ if encodes[e][2] == 'userdata' then
+ test:assertNotEquals(nil, encodes[e][1]:type(), 'check has :type()')
+ end
+ end
+
+end
+
+
+-- love.data.getPackedSize
+love.test.data.getPackedSize = function(test)
+ local pack1 = love.data.getPackedSize('>xI3b')
+ local pack2 = love.data.getPackedSize('>I2B')
+ local pack3 = love.data.getPackedSize('>I4I4I4I4x')
+ test:assertEquals(5, pack1, 'check pack size 1')
+ test:assertEquals(3, pack2, 'check pack size 2')
+ test:assertEquals(17, pack3, 'check pack size 3')
+end
+
+
+-- love.data.hash
+love.test.data.hash = function(test)
+ -- setup all the different hashing types
+ local str1 = love.data.hash('md5', 'helloworld')
+ local str2 = love.data.hash('sha1', 'helloworld')
+ local str3 = love.data.hash('sha224', 'helloworld')
+ local str4 = love.data.hash('sha256', 'helloworld')
+ local str5 = love.data.hash('sha384', 'helloworld')
+ local str6 = love.data.hash('sha512', 'helloworld')
+ -- check encoded hash value matches what's expected for that algo
+ test:assertEquals('fc5e038d38a57032085441e7fe7010b0', love.data.encode("string", "hex", str1), 'check md5 encode')
+ test:assertEquals('6adfb183a4a2c94a2f92dab5ade762a47889a5a1', love.data.encode("string", "hex", str2), 'check sha1 encode')
+ test:assertEquals('b033d770602994efa135c5248af300d81567ad5b59cec4bccbf15bcc', love.data.encode("string", "hex", str3), 'check sha224 encode')
+ test:assertEquals('936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af', love.data.encode("string", "hex", str4), 'check sha256 encode')
+ test:assertEquals('97982a5b1414b9078103a1c008c4e3526c27b41cdbcf80790560a40f2a9bf2ed4427ab1428789915ed4b3dc07c454bd9', love.data.encode("string", "hex", str5), 'check sha384 encode')
+ test:assertEquals('1594244d52f2d8c12b142bb61f47bc2eaf503d6d9ca8480cae9fcf112f66e4967dc5e8fa98285e36db8af1b8ffa8b84cb15e0fbcf836c3deb803c13f37659a60', love.data.encode("string", "hex", str6), 'check sha512 encode')
+end
+
+
+-- love.data.newByteData
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.data.newByteData = function(test)
+ test:assertObject(love.data.newByteData('helloworld'))
+end
+
+
+-- love.data.newDataView
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.data.newDataView = function(test)
+ test:assertObject(love.data.newDataView(love.data.newByteData('helloworld'), 0, 10))
+end
+
+
+-- love.data.pack
+love.test.data.pack = function(test)
+ local packed1 = love.data.pack('string', '>I4I4I4I4', 9999, 1000, 1010, 2030)
+ local packed2 = love.data.pack('data', '>I4I4I4I4', 9999, 1000, 1010, 2030)
+ local a, b, c, d = love.data.unpack('>I4I4I4I4', packed1)
+ local e, f, g, h = love.data.unpack('>I4I4I4I4', packed2)
+ test:assertEquals(9999+9999, a+e, 'check packed 1')
+ test:assertEquals(1000+1000, b+f, 'check packed 2')
+ test:assertEquals(1010+1010, c+g, 'check packed 3')
+ test:assertEquals(2030+2030, d+h, 'check packed 4')
+end
+
+
+-- love.data.unpack
+love.test.data.unpack = function(test)
+ local packed1 = love.data.pack('string', '>s5s4I3', 'hello', 'love', 100)
+ local packed2 = love.data.pack('data', '>s5I2', 'world', 20)
+ local a, b, c = love.data.unpack('>s5s4I3', packed1)
+ local d, e = love.data.unpack('>s5I2', packed2)
+ test:assertEquals(a .. ' ' .. d, 'hello world', 'check unpack 1')
+ test:assertEquals(b, 'love', 'check unpack 2')
+ test:assertEquals(c - e, 80, 'check unpack 3')
+end
diff --git a/testing/tests/event.lua b/testing/tests/event.lua
new file mode 100644
index 000000000..50d347a87
--- /dev/null
+++ b/testing/tests/event.lua
@@ -0,0 +1,80 @@
+-- love.event
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.event.clear
+love.test.event.clear = function(test)
+ -- push some events first
+ love.event.push('test', 1, 2, 3)
+ love.event.push('test', 1, 2, 3)
+ love.event.push('test', 1, 2, 3)
+ -- check after calling clear there are no events left
+ love.event.clear()
+ local count = 0
+ for n, a, b, c, d, e, f in love.event.poll() do
+ count = count + 1
+ end
+ test:assertEquals(0, count, 'check no events')
+end
+
+
+-- love.event.poll
+love.test.event.poll = function(test)
+ -- push some events first
+ love.event.push('test', 1, 2, 3)
+ love.event.push('test', 1, 2, 3)
+ love.event.push('test', 1, 2, 3)
+ -- check poll recieves all events
+ local count = 0
+ for n, a, b, c, d, e, f in love.event.poll() do
+ count = count + 1
+ end
+ test:assertEquals(3, count, 'check 3 events')
+end
+
+
+-- love.event.pump
+-- @NOTE dont think can really test as internally used
+love.test.event.pump = function(test)
+ test:skipTest('used internally')
+end
+
+
+-- love.event.push
+love.test.event.push = function(test)
+ -- check pushing some different types
+ love.event.push('add', 1, 2, 3)
+ love.event.push('ignore', 1, 2, 3)
+ love.event.push('add', 1, 2, 3)
+ love.event.push('ignore', 1, 2, 3)
+ local count = 0
+ for n, a, b, c, d, e, f in love.event.poll() do
+ if n == 'add' then
+ count = count + a + b + c
+ end
+ end
+ test:assertEquals(12, count, 'check total events')
+end
+
+
+-- love.event.quit
+love.test.event.quit = function(test)
+ -- setting this overrides the quit hook to prevent actually quitting
+ love.test.module.fakequit = true
+ love.event.quit(0)
+ -- if it failed we'd have quit here
+ test:assertTrue(true, 'check quit hook called')
+end
+
+
+-- love.event.wait
+-- @NOTE not sure best way to test this one
+love.test.event.wait = function(test)
+ test:skipTest('used internally')
+end
diff --git a/testing/tests/filesystem.lua b/testing/tests/filesystem.lua
new file mode 100644
index 000000000..55e112553
--- /dev/null
+++ b/testing/tests/filesystem.lua
@@ -0,0 +1,451 @@
+-- love.filesystem
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- File (love.filesystem.newFile)
+love.test.filesystem.File = function(test)
+
+ -- setup a file to play with
+ local file1 = love.filesystem.openFile('data.txt', 'w')
+ file1:write('helloworld')
+ test:assertObject(file1)
+ file1:close()
+
+ -- test read mode
+ file1:open('r')
+ test:assertEquals('r', file1:getMode(), 'check read mode')
+ local contents, size = file1:read()
+ test:assertEquals('helloworld', contents)
+ test:assertEquals(10, size, 'check file read')
+ test:assertEquals(10, file1:getSize())
+ local ok1, err1 = file1:write('hello')
+ test:assertNotEquals(nil, err1, 'check cant write in read mode')
+ local iterator = file1:lines()
+ test:assertNotEquals(nil, iterator, 'check can read lines')
+ test:assertEquals('data.txt', file1:getFilename(), 'check filename matches')
+ file1:close()
+
+ -- test write mode
+ file1:open('w')
+ test:assertEquals('w', file1:getMode(), 'check write mode')
+ contents, size = file1:read()
+ test:assertEquals(nil, contents, 'check cant read file in write mode')
+ test:assertEquals('string', type(size), 'check err message shown')
+ local ok2, err2 = file1:write('helloworld')
+ test:assertTrue(ok2, 'check file write')
+ test:assertEquals(nil, err2, 'check no err writing')
+
+ -- test open/closing
+ file1:open('r')
+ test:assertTrue(file1:isOpen(), 'check file is open')
+ file1:close()
+ test:assertFalse(file1:isOpen(), 'check file gets closed')
+ file1:close()
+
+ -- test buffering and flushing
+ file1:open('w')
+ local ok3, err3 = file1:setBuffer('full', 10000)
+ test:assertTrue(ok3)
+ test:assertEquals('full', file1:getBuffer())
+ file1:write('replacedcontent')
+ file1:flush()
+ file1:close()
+ file1:open('r')
+ contents, size = file1:read()
+ test:assertEquals('replacedcontent', contents, 'check buffered content was written')
+ file1:close()
+
+ -- loop through file data with seek/tell until EOF
+ file1:open('r')
+ local counter = 0
+ for i=1,100 do
+ file1:seek(i)
+ test:assertEquals(i, file1:tell())
+ if file1:isEOF() == true then
+ counter = i
+ break
+ end
+ end
+ test:assertEquals(counter, 15)
+ file1:close()
+
+end
+
+
+-- FileData (love.filesystem.newFileData)
+love.test.filesystem.FileData = function(test)
+
+ -- create new obj
+ local fdata = love.filesystem.newFileData('helloworld', 'test.txt')
+ test:assertObject(fdata)
+ test:assertEquals('test.txt', fdata:getFilename())
+ test:assertEquals('txt', fdata:getExtension())
+
+ -- check properties match expected
+ test:assertEquals('helloworld', fdata:getString(), 'check data string')
+ test:assertEquals(10, fdata:getSize(), 'check data size')
+
+ -- check cloning the bytedata
+ local clonedfdata = fdata:clone()
+ test:assertObject(clonedfdata)
+ test:assertEquals('helloworld', clonedfdata:getString(), 'check cloned data')
+ test:assertEquals(10, clonedfdata:getSize(), 'check cloned size')
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.filesystem.append
+love.test.filesystem.append = function(test)
+ -- create a new file to test with
+ love.filesystem.write('filesystem.append.txt', 'foo')
+ -- try appending text and check new file contents/size matches
+ local success, message = love.filesystem.append('filesystem.append.txt', 'bar')
+ test:assertNotEquals(false, success, 'check success')
+ test:assertEquals(nil, message, 'check no error msg')
+ local contents, size = love.filesystem.read('filesystem.append.txt')
+ test:assertEquals(contents, 'foobar', 'check file contents')
+ test:assertEquals(size, 6, 'check file size')
+ -- check appending a specific no. of bytes
+ love.filesystem.append('filesystem.append.txt', 'foobarfoobarfoo', 6)
+ contents, size = love.filesystem.read('filesystem.append.txt')
+ test:assertEquals(contents, 'foobarfoobar', 'check appended contents')
+ test:assertEquals(size, 12, 'check appended size')
+ -- cleanup
+ love.filesystem.remove('filesystem.append.txt')
+end
+
+
+-- love.filesystem.areSymlinksEnabled
+-- @NOTE best can do here is just check not nil
+love.test.filesystem.areSymlinksEnabled = function(test)
+ test:assertNotNil(love.filesystem.areSymlinksEnabled())
+end
+
+
+-- love.filesystem.createDirectory
+love.test.filesystem.createDirectory = function(test)
+ -- try creating a dir + subdir and check both exist
+ local success = love.filesystem.createDirectory('foo/bar')
+ test:assertNotEquals(false, success, 'check success')
+ test:assertNotEquals(nil, love.filesystem.getInfo('foo', 'directory'), 'check directory created')
+ test:assertNotEquals(nil, love.filesystem.getInfo('foo/bar', 'directory'), 'check subdirectory created')
+ -- cleanup
+ love.filesystem.remove('foo/bar')
+ love.filesystem.remove('foo')
+end
+
+
+-- love.filesystem.getAppdataDirectory
+-- @NOTE i think this is too platform dependent to be tested nicely
+love.test.filesystem.getAppdataDirectory = function(test)
+ test:assertNotNil(love.filesystem.getAppdataDirectory())
+end
+
+
+-- love.filesystem.getCRequirePath
+love.test.filesystem.getCRequirePath = function(test)
+ -- check default value from documentation
+ test:assertEquals('??', love.filesystem.getCRequirePath(), 'check default value')
+end
+
+
+-- love.filesystem.getDirectoryItems
+love.test.filesystem.getDirectoryItems = function(test)
+ -- create a dir + subdir with 2 files
+ love.filesystem.createDirectory('foo/bar')
+ love.filesystem.write('foo/file1.txt', 'file1')
+ love.filesystem.write('foo/bar/file2.txt', 'file2')
+ -- check both the file + subdir exist in the item list
+ local files = love.filesystem.getDirectoryItems('foo')
+ local hasfile = false
+ local hasdir = false
+ for _,v in ipairs(files) do
+ local info = love.filesystem.getInfo('foo/'..v)
+ if v == 'bar' and info.type == 'directory' then hasdir = true end
+ if v == 'file1.txt' and info.type == 'file' then hasfile = true end
+ end
+ test:assertTrue(hasfile, 'check file exists')
+ test:assertTrue(hasdir, 'check directory exists')
+ -- cleanup
+ love.filesystem.remove('foo/file1.txt')
+ love.filesystem.remove('foo/bar/file2.txt')
+ love.filesystem.remove('foo/bar')
+ love.filesystem.remove('foo')
+end
+
+
+-- love.filesystem.getIdentity
+love.test.filesystem.getIdentity = function(test)
+ -- check setting identity matches
+ local original = love.filesystem.getIdentity()
+ love.filesystem.setIdentity('lover')
+ test:assertEquals('lover', love.filesystem.getIdentity(), 'check identity matches')
+ -- put back to original value
+ love.filesystem.setIdentity(original)
+end
+
+
+-- love.filesystem.getRealDirectory
+love.test.filesystem.getRealDirectory = function(test)
+ -- make a test dir + file first
+ love.filesystem.createDirectory('foo')
+ love.filesystem.write('foo/test.txt', 'test')
+ -- check save dir matches the real dir we just wrote to
+ test:assertEquals(love.filesystem.getSaveDirectory(),
+ love.filesystem.getRealDirectory('foo/test.txt'), 'check directory matches')
+ -- cleanup
+ love.filesystem.remove('foo/test.txt')
+ love.filesystem.remove('foo')
+end
+
+
+-- love.filesystem.getRequirePath
+love.test.filesystem.getRequirePath = function(test)
+ test:assertEquals('?.lua;?/init.lua',
+ love.filesystem.getRequirePath(), 'check default value')
+end
+
+
+-- love.filesystem.getSource
+-- @NOTE i dont think we can test this cos love calls it first
+love.test.filesystem.getSource = function(test)
+ test:skipTest('used internally')
+end
+
+
+-- love.filesystem.getSourceBaseDirectory
+-- @NOTE i think this is too platform dependent to be tested nicely
+love.test.filesystem.getSourceBaseDirectory = function(test)
+ test:assertNotNil(love.filesystem.getSourceBaseDirectory())
+end
+
+
+-- love.filesystem.getUserDirectory
+-- @NOTE i think this is too platform dependent to be tested nicely
+love.test.filesystem.getUserDirectory = function(test)
+ test:assertNotNil(love.filesystem.getUserDirectory())
+end
+
+
+-- love.filesystem.getWorkingDirectory
+-- @NOTE i think this is too platform dependent to be tested nicely
+love.test.filesystem.getWorkingDirectory = function(test)
+ test:assertNotNil(love.filesystem.getWorkingDirectory())
+end
+
+
+-- love.filesystem.getSaveDirectory
+-- @NOTE i think this is too platform dependent to be tested nicely
+love.test.filesystem.getSaveDirectory = function(test)
+ test:assertNotNil(love.filesystem.getSaveDirectory())
+end
+
+
+-- love.filesystem.getInfo
+love.test.filesystem.getInfo = function(test)
+ -- create a dir and subdir with a file
+ love.filesystem.createDirectory('foo/bar')
+ love.filesystem.write('foo/bar/file2.txt', 'file2')
+ -- check getinfo returns the correct values
+ test:assertEquals(nil, love.filesystem.getInfo('foo/bar/file2.txt', 'directory'), 'check not directory')
+ test:assertNotEquals(nil, love.filesystem.getInfo('foo/bar/file2.txt'), 'check info not nil')
+ test:assertEquals(love.filesystem.getInfo('foo/bar/file2.txt').size, 5, 'check info size match')
+ -- @TODO test modified timestamp from info.modtime?
+ -- cleanup
+ love.filesystem.remove('foo/bar/file2.txt')
+ love.filesystem.remove('foo/bar')
+ love.filesystem.remove('foo')
+end
+
+
+-- love.filesystem.isFused
+love.test.filesystem.isFused = function(test)
+ -- kinda assuming you'd run the testsuite in a non-fused game
+ test:assertEquals(love.filesystem.isFused(), false, 'check not fused')
+end
+
+
+-- love.filesystem.lines
+love.test.filesystem.lines = function(test)
+ -- check lines returns the 3 lines expected
+ love.filesystem.write('file.txt', 'line1\nline2\nline3')
+ local linenum = 1
+ for line in love.filesystem.lines('file.txt') do
+ test:assertEquals('line' .. tostring(linenum), line, 'check line matches')
+ -- also check it removes newlines like the docs says it does
+ test:assertEquals(nil, string.find(line, '\n'), 'check newline removed')
+ linenum = linenum + 1
+ end
+ -- cleanup
+ love.filesystem.remove('file.txt')
+end
+
+
+-- love.filesystem.load
+love.test.filesystem.load = function(test)
+ -- setup some fake lua files
+ love.filesystem.write('test1.lua', 'function test()\nreturn 1\nend\nreturn test()')
+ love.filesystem.write('test2.lua', 'function test()\nreturn 1')
+ -- check file that doesn't exist
+ local chunk, errormsg = love.filesystem.load('faker.lua')
+ test:assertEquals(nil, chunk, 'check file doesnt exist')
+ -- check valid lua file
+ chunk, errormsg = love.filesystem.load('test1.lua')
+ test:assertEquals(nil, errormsg, 'check no error message')
+ test:assertEquals(1, chunk(), 'check lua file runs')
+ -- check invalid lua file
+ local ok, chunk, err = pcall(love.filesystem.load, 'test2.lua')
+ test:assertFalse(ok, 'check invalid lua file')
+ -- cleanup
+ love.filesystem.remove('test1.lua')
+ love.filesystem.remove('test2.lua')
+end
+
+
+-- love.filesystem.mount
+love.test.filesystem.mount = function(test)
+ -- write an example zip to savedir to use
+ local contents, size = love.filesystem.read('resources/test.zip') -- contains test.txt
+ love.filesystem.write('test.zip', contents, size)
+ -- check mounting file and check contents are mounted
+ local success = love.filesystem.mount('test.zip', 'test')
+ test:assertTrue(success, 'check success')
+ test:assertNotEquals(nil, love.filesystem.getInfo('test'), 'check mount not nil')
+ test:assertEquals('directory', love.filesystem.getInfo('test').type, 'check directory made')
+ test:assertNotEquals(nil, love.filesystem.getInfo('test/test.txt'), 'check file not nil')
+ test:assertEquals('file', love.filesystem.getInfo('test/test.txt').type, 'check file type')
+ -- cleanup
+ love.filesystem.remove('test/test.txt')
+ love.filesystem.remove('test')
+ love.filesystem.remove('test.zip')
+end
+
+
+-- love.filesystem.openFile
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.filesystem.openFile = function(test)
+ test:assertNotNil(love.filesystem.openFile('file2.txt', 'w'))
+ test:assertNotNil(love.filesystem.openFile('file2.txt', 'r'))
+ test:assertNotNil(love.filesystem.openFile('file2.txt', 'a'))
+ test:assertNotNil(love.filesystem.openFile('file2.txt', 'c'))
+ love.filesystem.remove('file2.txt')
+end
+
+
+-- love.filesystem.newFileData
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.filesystem.newFileData = function(test)
+ test:assertNotNil(love.filesystem.newFileData('helloworld', 'file1'))
+end
+
+
+-- love.filesystem.read
+love.test.filesystem.read = function(test)
+ -- check reading a full file
+ local content, size = love.filesystem.read('resources/test.txt')
+ test:assertNotEquals(nil, content, 'check not nil')
+ test:assertEquals('helloworld', content, 'check content match')
+ test:assertEquals(10, size, 'check size match')
+ -- check reading partial file
+ content, size = love.filesystem.read('resources/test.txt', 5)
+ test:assertNotEquals(nil, content, 'check not nil')
+ test:assertEquals('hello', content, 'check content match')
+ test:assertEquals(5, size, 'check size match')
+end
+
+
+-- love.filesystem.remove
+love.test.filesystem.remove = function(test)
+ -- create a dir + subdir with a file
+ love.filesystem.createDirectory('foo/bar')
+ love.filesystem.write('foo/bar/file2.txt', 'helloworld')
+ -- check removing files + dirs (should fail to remove dir if file inside)
+ test:assertFalse(love.filesystem.remove('foo'), 'check fail when file inside')
+ test:assertFalse(love.filesystem.remove('foo/bar'), 'check fail when file inside')
+ test:assertTrue(love.filesystem.remove('foo/bar/file2.txt'), 'check file removed')
+ test:assertTrue(love.filesystem.remove('foo/bar'), 'check subdirectory removed')
+ test:assertTrue(love.filesystem.remove('foo'), 'check directory removed')
+ -- cleanup not needed here hopefully...
+end
+
+
+-- love.filesystem.setCRequirePath
+love.test.filesystem.setCRequirePath = function(test)
+ -- check setting path val is returned
+ love.filesystem.setCRequirePath('/??')
+ test:assertEquals('/??', love.filesystem.getCRequirePath(), 'check crequirepath value')
+ love.filesystem.setCRequirePath('??')
+end
+
+
+-- love.filesystem.setIdentity
+love.test.filesystem.setIdentity = function(test)
+ -- check setting identity val is returned
+ local original = love.filesystem.getIdentity()
+ love.filesystem.setIdentity('lover')
+ test:assertEquals('lover', love.filesystem.getIdentity(), 'check indentity value')
+ -- return value to original
+ love.filesystem.setIdentity(original)
+end
+
+
+-- love.filesystem.setRequirePath
+love.test.filesystem.setRequirePath = function(test)
+ -- check setting path val is returned
+ love.filesystem.setRequirePath('?.lua;?/start.lua')
+ test:assertEquals('?.lua;?/start.lua', love.filesystem.getRequirePath(), 'check require path')
+ -- reset to default
+ love.filesystem.setRequirePath('?.lua;?/init.lua')
+end
+
+
+-- love.filesystem.setSource
+love.test.filesystem.setSource = function(test)
+ test:skipTest('used internally')
+end
+
+
+-- love.filesystem.unmount
+love.test.filesystem.unmount = function(test)
+ -- create a zip file mounted to use
+ local contents, size = love.filesystem.read('resources/test.zip') -- contains test.txt
+ love.filesystem.write('test.zip', contents, size)
+ love.filesystem.mount('test.zip', 'test')
+ -- check mounted, unmount, then check its unmounted
+ test:assertNotEquals(nil, love.filesystem.getInfo('test/test.txt'), 'check mount exists')
+ love.filesystem.unmount('test.zip')
+ test:assertEquals(nil, love.filesystem.getInfo('test/test.txt'), 'check unmounted')
+ -- cleanup
+ love.filesystem.remove('test/test.txt')
+ love.filesystem.remove('test')
+ love.filesystem.remove('test.zip')
+end
+
+
+-- love.filesystem.write
+love.test.filesystem.write = function(test)
+ -- check writing a bunch of files matches whats read back
+ love.filesystem.write('test1.txt', 'helloworld')
+ love.filesystem.write('test2.txt', 'helloworld', 10)
+ love.filesystem.write('test3.txt', 'helloworld', 5)
+ test:assertEquals('helloworld', love.filesystem.read('test1.txt'), 'check read file')
+ test:assertEquals('helloworld', love.filesystem.read('test2.txt'), 'check read all')
+ test:assertEquals('hello', love.filesystem.read('test3.txt'), 'check read partial')
+ -- cleanup
+ love.filesystem.remove('test1.txt')
+ love.filesystem.remove('test2.txt')
+ love.filesystem.remove('test3.txt')
+end
diff --git a/testing/tests/font.lua b/testing/tests/font.lua
new file mode 100644
index 000000000..acd7eed12
--- /dev/null
+++ b/testing/tests/font.lua
@@ -0,0 +1,131 @@
+-- love.font
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- GlyphData (love.font.newGlyphData)
+love.test.font.GlyphData = function(test)
+
+ -- create obj
+ local rasterizer = love.font.newRasterizer('resources/font.ttf')
+ local gdata = love.font.newGlyphData(rasterizer, 97) -- 'a'
+ test:assertObject(gdata)
+
+ -- check properties match expected
+ test:assertNotNil(gdata:getString())
+ test:assertEquals(128, gdata:getSize(), 'check data size')
+ test:assertEquals(9, gdata:getAdvance(), 'check advance')
+ test:assertEquals('la8', gdata:getFormat(), 'check format')
+
+ -- @TODO
+ --[[
+ currently these will return 0 and '' respectively as not implemented
+ https://github.com/love2d/love/blob/12.0-development/src/modules/font/freetype/TrueTypeRasterizer.cpp#L140-L141
+ "basically I haven't decided what to do here yet, because of the more
+ advanced text shaping that happens in love 12 having a unicode codepoint
+ associated with a glyph probably doesn't make sense in the first place"
+ ]]--
+ --test:assertEquals(97, gdata:getGlyph(), 'check glyph number') - returns 0
+ --test:assertEquals('a', gdata:getGlyphString(), 'check glyph string') - returns ''
+
+ -- check height + width
+ test:assertEquals(8, gdata:getHeight(), 'check height')
+ test:assertEquals(8, gdata:getWidth(), 'check width')
+
+ -- check boundary / dimensions
+ local x, y, w, h = gdata:getBoundingBox()
+ local dw, dh = gdata:getDimensions()
+ test:assertEquals(0, x, 'check bbox x')
+ test:assertEquals(-3, y, 'check bbox y')
+ test:assertEquals(8, w, 'check bbox w')
+ test:assertEquals(14, h, 'check bbox h')
+ test:assertEquals(8, dw, 'check dim width')
+ test:assertEquals(8, dh, 'check dim height')
+
+ -- check bearing
+ local bw, bh = gdata:getBearing()
+ test:assertEquals(0, bw, 'check bearing w')
+ test:assertEquals(11, bh, 'check bearing h')
+
+end
+
+
+-- Rasterizer (love.font.newRasterizer)
+love.test.font.Rasterizer = function(test)
+
+ -- create obj
+ local rasterizer = love.font.newRasterizer('resources/font.ttf')
+ test:assertObject(rasterizer)
+
+ -- check advance
+ test:assertEquals(9, rasterizer:getAdvance(), 'check advance')
+
+ -- check ascent/descent
+ test:assertEquals(9, rasterizer:getAscent(), 'check ascent')
+ test:assertEquals(-3, rasterizer:getDescent(), 'check descent')
+
+ -- check glyphcount
+ test:assertEquals(77, rasterizer:getGlyphCount(), 'check glyph count')
+
+ -- check specific glyphs
+ test:assertObject(rasterizer:getGlyphData('L'))
+ test:assertTrue(rasterizer:hasGlyphs('L', 'O', 'V', 'E'), 'check LOVE')
+
+ -- check height + lineheight
+ test:assertEquals(12, rasterizer:getHeight(), 'check height')
+ test:assertEquals(15, rasterizer:getLineHeight(), 'check line height')
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.font.newBMFontRasterizer
+love.test.font.newBMFontRasterizer = function(test)
+ local rasterizer = love.font.newBMFontRasterizer('resources/love.png');
+ test:assertObject(rasterizer)
+end
+
+
+-- love.font.newGlyphData
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.font.newGlyphData = function(test)
+ local img = love.image.newImageData('resources/love.png')
+ local rasterizer = love.font.newImageRasterizer(img, 'ABC', 0, 1);
+ local glyphdata = love.font.newGlyphData(rasterizer, 65)
+ test:assertObject(glyphdata)
+end
+
+
+-- love.font.newImageRasterizer
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.font.newImageRasterizer = function(test)
+ local img = love.image.newImageData('resources/love.png')
+ local rasterizer = love.font.newImageRasterizer(img, 'ABC', 0, 1);
+ test:assertObject(rasterizer)
+end
+
+
+-- love.font.newRasterizer
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.font.newRasterizer = function(test)
+ test:assertObject(love.font.newRasterizer('resources/font.ttf'))
+end
+
+
+-- love.font.newTrueTypeRasterizer
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.font.newTrueTypeRasterizer = function(test)
+ test:assertObject(love.font.newTrueTypeRasterizer(12, "normal", 1))
+ test:assertObject(love.font.newTrueTypeRasterizer('resources/font.ttf', 8, "normal", 1))
+end
diff --git a/testing/tests/graphics.lua b/testing/tests/graphics.lua
new file mode 100644
index 000000000..7c6f7d928
--- /dev/null
+++ b/testing/tests/graphics.lua
@@ -0,0 +1,2743 @@
+-- love.graphics
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- Canvas (love.graphics.newCanvas)
+love.test.graphics.Canvas = function(test)
+
+ -- create canvas with defaults
+ local canvas = love.graphics.newCanvas(100, 100, {
+ type = '2d',
+ format = 'normal',
+ readable = true,
+ msaa = 0,
+ dpiscale = love.graphics.getDPIScale(),
+ mipmaps = 'auto'
+ })
+ test:assertObject(canvas)
+
+ -- check dpi
+ test:assertEquals(love.graphics.getDPIScale(), canvas:getDPIScale(), 'check dpi scale')
+
+ -- check depth
+ test:assertEquals(1, canvas:getDepth(), 'check depth is 2d')
+ test:assertEquals(nil, canvas:getDepthSampleMode(), 'check depth sample nil')
+
+ -- check fliter
+ local min1, mag1, ani1 = canvas:getFilter()
+ test:assertEquals('nearest', min1, 'check filter def min')
+ test:assertEquals('nearest', mag1, 'check filter def mag')
+ test:assertEquals(1, ani1, 'check filter def ani')
+ canvas:setFilter('linear', 'linear', 2)
+ local min2, mag2, ani2 = canvas:getFilter()
+ test:assertEquals('linear', min2, 'check filter changed min')
+ test:assertEquals('linear', mag2, 'check filter changed mag')
+ test:assertEquals(2, ani2, 'check filter changed ani')
+
+ -- check layer
+ test:assertEquals(1, canvas:getLayerCount(), 'check 1 layer for 2d')
+
+ -- check texture type
+ test:assertEquals('2d', canvas:getTextureType(), 'check 2d')
+
+ -- check texture wrap
+ local horiz1, vert1 = canvas:getWrap()
+ test:assertEquals('clamp', horiz1, 'check def wrap h')
+ test:assertEquals('clamp', vert1, 'check def wrap v')
+ canvas:setWrap('repeat', 'repeat')
+ local horiz2, vert2 = canvas:getWrap()
+ test:assertEquals('repeat', horiz2, 'check changed wrap h')
+ test:assertEquals('repeat', vert2, 'check changed wrap v')
+
+ -- check readable
+ test:assertTrue(canvas:isReadable(), 'check canvas readable')
+
+ -- check msaa
+ test:assertEquals(1, canvas:getMSAA(), 'check samples match')
+
+ -- check dimensions
+ local cw, ch = canvas:getDimensions()
+ test:assertEquals(100, cw, 'check canvas dim w')
+ test:assertEquals(100, ch, 'check canvas dim h')
+ test:assertEquals(cw, canvas:getWidth(), 'check canvas w matches dim')
+ test:assertEquals(ch, canvas:getHeight(), 'check canvas h matches dim')
+ local pw, ph = canvas:getPixelDimensions()
+ test:assertEquals(100*love.graphics.getDPIScale(), pw, 'check pixel dim w')
+ test:assertEquals(100*love.graphics.getDPIScale(), ph, 'check pixel dim h')
+ test:assertEquals(pw, canvas:getPixelWidth(), 'check pixel w matches dim')
+ test:assertEquals(ph, canvas:getPixelHeight(), 'check pixel h matches dim')
+
+ -- check mipmaps
+ local mode, sharpness = canvas:getMipmapFilter()
+ test:assertEquals('linear', mode, 'check def minmap filter mode')
+ test:assertEquals(0, sharpness, 'check def minmap filter sharpness')
+ local name, version, vendor, device = love.graphics.getRendererInfo()
+ canvas:setMipmapFilter('nearest', 1)
+ mode, sharpness = canvas:getMipmapFilter()
+ test:assertEquals('nearest', mode, 'check changed minmap filter mode')
+ -- @NOTE mipmap sharpness wont work on opengl/metal
+ if string.match(name, 'OpenGL ES') == nil and string.match(name, 'Metal') == nil then
+ test:assertEquals(1, sharpness, 'check changed minmap filter sharpness')
+ end
+ test:assertGreaterEqual(2, canvas:getMipmapCount()) -- docs say no mipmaps should return 1
+ test:assertEquals('auto', canvas:getMipmapMode())
+
+ -- check basic rendering
+ canvas:renderTo(function()
+ love.graphics.setColor(1, 0, 0)
+ love.graphics.rectangle('fill', 0, 0, 200, 200)
+ love.graphics.setColor(1, 1, 1, 1)
+ end)
+ local imgdata1 = love.graphics.readbackTexture(canvas, {100, 0, 0, 0, 100, 100})
+ test:assertPixels(imgdata1, {
+ red = {{0, 0},{0,99},{99,0},{99,99}},
+ }, 'font draw check')
+ test:compareImg(imgdata1)
+
+ -- check using canvas in love.graphics.draw()
+ local xcanvas = love.graphics.newCanvas()
+ love.graphics.setCanvas(xcanvas)
+ love.graphics.draw(canvas, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata2 = love.graphics.readbackTexture(canvas, {100, 0, 0, 0, 100, 100})
+ test:assertPixels(imgdata2, {
+ red = {{0, 0},{0,99},{99,0},{99,99}},
+ }, 'font draw check')
+ test:compareImg(imgdata2)
+
+ -- check depth samples
+ local dcanvas = love.graphics.newCanvas(100, 100, {
+ type = '2d',
+ format = 'depth16',
+ readable = true
+ })
+ test:assertEquals(nil, dcanvas:getDepthSampleMode(), 'check depth sample mode nil by def')
+ dcanvas:setDepthSampleMode('equal')
+ test:assertEquals('equal', dcanvas:getDepthSampleMode(), 'check depth sample mode set')
+
+end
+
+
+-- Font (love.graphics.newFont)
+love.test.graphics.Font = function(test)
+
+ -- create obj
+ local font = love.graphics.newFont('resources/font.ttf', 8)
+ test:assertObject(font)
+
+ -- check ascent/descent
+ test:assertEquals(6, font:getAscent(), 'check ascent')
+ test:assertEquals(-2, font:getDescent(), 'check descent')
+
+ -- check baseline
+ test:assertEquals(6, font:getBaseline(), 'check baseline')
+
+ -- check dpi
+ test:assertEquals(1, font:getDPIScale(), 'check dpi')
+
+ -- check filter
+ test:assertEquals('nearest', font:getFilter(), 'check filter def')
+ font:setFilter('linear', 'linear')
+ test:assertEquals('linear', font:getFilter(), 'check filter change')
+ font:setFilter('nearest', 'nearest')
+
+ -- check height + lineheight
+ test:assertEquals(8, font:getHeight(), 'check height')
+ test:assertEquals(1, font:getLineHeight(), 'check line height')
+ font:setLineHeight(2)
+ test:assertEquals(2, font:getLineHeight(), 'check changed line height')
+ font:setLineHeight(1) -- reset for drawing + wrap later
+
+ -- check width + kerning
+ test:assertEquals(0, font:getKerning('a', 'b'), 'check kerning')
+ test:assertEquals(24, font:getWidth('test'), 'check data size')
+
+ -- check specific glyphs
+ test:assertTrue(font:hasGlyphs('test'), 'check data size')
+
+ -- check font wrapping
+ local width, wrappedtext = font:getWrap('LÖVE is an *awesome* framework you can use to make 2D games in Lua.', 50)
+ test:assertEquals(48, width, 'check actual wrap width')
+ test:assertEquals(8, #wrappedtext, 'check wrapped lines')
+ test:assertEquals('LÖVE is an ', wrappedtext[1], 'check wrapped line')
+
+ -- check drawing font
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.setFont(font)
+ love.graphics.print('Aa', 0, 5)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ white = {{0,3},{4,3},{7,4},{9,4},{10,5},{0,8},{4,8},{10,8}},
+ }, 'font draw check')
+ test:compareImg(imgdata)
+
+ -- check font substitution
+ local fontab = love.graphics.newImageFont('resources/font-letters-ab.png', 'AB')
+ local fontcd = love.graphics.newImageFont('resources/font-letters-cd.png', 'CD')
+ fontab:setFallbacks(fontcd)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 0)
+ love.graphics.setFont(fontab)
+ love.graphics.print('AB', 0, 0) -- should come from fontab
+ love.graphics.print('CD', 0, 9) -- should come from fontcd
+ love.graphics.setCanvas()
+ local imgdata2 = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata2, {
+ green = {{1,8},{6,8},{2,10},{5,10},{9,10}},
+ black = {{9,9},{14,8},{14,10},{14,1},{1,10}}
+ }, 'font draw check')
+ test:compareImg(imgdata2)
+
+end
+
+
+-- Image (love.graphics.newImage)
+love.test.graphics.Image = function(test)
+
+ -- create object
+ local image = love.graphics.newImage('resources/love.png', {
+ dpiscale = 1,
+ mipmaps = true
+ })
+ test:assertObject(image)
+
+ -- check dpi
+ test:assertEquals(love.graphics.getDPIScale(), image:getDPIScale(), 'check dpi scale')
+
+ -- check depth
+ test:assertEquals(1, image:getDepth(), 'check depth is 2d')
+ test:assertEquals(nil, image:getDepthSampleMode(), 'check depth sample nil')
+
+ -- check filter
+ local min1, mag1, ani1 = image:getFilter()
+ test:assertEquals('nearest', min1, 'check filter def min')
+ test:assertEquals('nearest', mag1, 'check filter def mag')
+ test:assertEquals(1, ani1, 'check filter def ani')
+ image:setFilter('linear', 'linear', 2)
+ local min2, mag2, ani2 = image:getFilter()
+ test:assertEquals('linear', min2, 'check filter changed min')
+ test:assertEquals('linear', mag2, 'check filter changed mag')
+ test:assertEquals(2, ani2, 'check filter changed ani')
+ image:setFilter('nearest', 'nearest', 1)
+
+ -- check layers
+ test:assertEquals(1, image:getLayerCount(), 'check 1 layer for 2d')
+
+ -- check texture type
+ test:assertEquals('2d', image:getTextureType(), 'check 2d')
+
+ -- check texture wrapping
+ local horiz1, vert1 = image:getWrap()
+ test:assertEquals('clamp', horiz1, 'check def wrap h')
+ test:assertEquals('clamp', vert1, 'check def wrap v')
+ image:setWrap('repeat', 'repeat')
+ local horiz2, vert2 = image:getWrap()
+ test:assertEquals('repeat', horiz2, 'check changed wrap h')
+ test:assertEquals('repeat', vert2, 'check changed wrap v')
+
+ -- check readable
+ test:assertTrue(image:isReadable(), 'check canvas readable')
+
+ -- check msaa
+ test:assertEquals(1, image:getMSAA(), 'check samples match')
+
+ -- check dimensions
+ local cw, ch = image:getDimensions()
+ test:assertEquals(64, cw, 'check canvas dim w')
+ test:assertEquals(64, ch, 'check canvas dim h')
+ test:assertEquals(cw, image:getWidth(), 'check canvas w matches dim')
+ test:assertEquals(ch, image:getHeight(), 'check canvas h matches dim')
+ local pw, ph = image:getPixelDimensions()
+ test:assertEquals(64*love.graphics.getDPIScale(), pw, 'check pixel dim w')
+ test:assertEquals(64*love.graphics.getDPIScale(), ph, 'check pixel dim h')
+ test:assertEquals(pw, image:getPixelWidth(), 'check pixel w matches dim')
+ test:assertEquals(ph, image:getPixelHeight(), 'check pixel h matches dim')
+
+ -- check mipmaps
+ local mode, sharpness = image:getMipmapFilter()
+ test:assertEquals('linear', mode, 'check def minmap filter mode')
+ test:assertEquals(0, sharpness, 'check def minmap filter sharpness')
+ local name, version, vendor, device = love.graphics.getRendererInfo()
+ -- @note mipmap sharpness wont work on opengl/metal
+ image:setMipmapFilter('nearest', 1)
+ mode, sharpness = image:getMipmapFilter()
+ test:assertEquals('nearest', mode, 'check changed minmap filter mode')
+ if string.match(name, 'OpenGL ES') == nil and string.match(name, 'Metal') == nil then
+ test:assertEquals(1, sharpness, 'check changed minmap filter sharpness')
+ end
+ test:assertGreaterEqual(2, image:getMipmapCount()) -- docs say no mipmaps should return 1?
+
+ -- check image properties
+ test:assertFalse(image:isCompressed(), 'check not compressed')
+ test:assertFalse(image:isFormatLinear(), 'check not linear')
+ local cimage = love.graphics.newImage('resources/love.dxt1')
+ test:assertObject(cimage)
+ test:assertTrue(cimage:isCompressed(), 'check is compressed')
+
+ -- check pixel replacement
+ local rimage = love.image.newImageData('resources/loveinv.png')
+ image:replacePixels(rimage)
+ local canvas = love.graphics.newCanvas(64, 64)
+ love.graphics.setCanvas(canvas)
+ love.graphics.draw(image, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ local r1, g1, b1 = imgdata:getPixel(25, 25)
+ test:assertEquals(3, r1+g1+b1, 'check back to white')
+ test:compareImg(imgdata)
+
+end
+
+
+-- Mesh (love.graphics.newMesh)
+love.test.graphics.Mesh = function(test)
+
+ -- create 2d mesh with pretty colors
+ local image = love.graphics.newImage('resources/love.png')
+ local vertices = {
+ { 0, 0, 0, 0, 1, 0, 0 },
+ { image:getWidth(), 0, 1, 0, 0, 1, 0 },
+ { image:getWidth(), image:getHeight(), 1, 1, 0, 0, 1 },
+ { 0, image:getHeight(), 0, 1, 1, 1, 0 },
+ }
+ local mesh1 = love.graphics.newMesh(vertices, 'fan')
+ test:assertObject(mesh1)
+
+ -- check draw mode
+ test:assertEquals('fan', mesh1:getDrawMode(), 'check draw mode')
+ mesh1:setDrawMode('triangles')
+ test:assertEquals('triangles', mesh1:getDrawMode(), 'check draw mode set')
+
+ -- check draw range
+ local min1, max1 = mesh1:getDrawRange()
+ test:assertEquals(nil, min1, 'check draw range not set')
+ mesh1:setDrawRange(1, 10)
+ local min2, max2 = mesh1:getDrawRange()
+ test:assertEquals(1, min2, 'check draw range set min')
+ test:assertEquals(10, max2, 'check draw range set max')
+
+ -- check texture pointer
+ test:assertEquals(nil, mesh1:getTexture(), 'check no texture')
+ mesh1:setTexture(image)
+ test:assertEquals(image:getHeight(), mesh1:getTexture():getHeight(), 'check texture match w')
+ test:assertEquals(image:getWidth(), mesh1:getTexture():getWidth(), 'check texture match h')
+
+ -- check vertext count
+ test:assertEquals(4, mesh1:getVertexCount(), 'check vertex count')
+
+ -- check def vertex format
+ local format = mesh1:getVertexFormat()
+ test:assertEquals('floatvec2', format[2][2], 'check def vertex format 2')
+ test:assertEquals('VertexColor', format[3][1], 'check def vertex format 3')
+
+ -- check vertext attributes
+ test:assertTrue(mesh1:isAttributeEnabled('VertexPosition'), 'check def attribute VertexPosition')
+ test:assertTrue(mesh1:isAttributeEnabled('VertexTexCoord'), 'check def attribute VertexTexCoord')
+ test:assertTrue(mesh1:isAttributeEnabled('VertexColor'), 'check def attribute VertexColor')
+ mesh1:setAttributeEnabled('VertexPosition', false)
+ mesh1:setAttributeEnabled('VertexTexCoord', false)
+ mesh1:setAttributeEnabled('VertexColor', false)
+ test:assertFalse(mesh1:isAttributeEnabled('VertexPosition'), 'check disable attribute VertexPosition')
+ test:assertFalse(mesh1:isAttributeEnabled('VertexTexCoord'), 'check disable attribute VertexTexCoord')
+ test:assertFalse(mesh1:isAttributeEnabled('VertexColor'), 'check disable attribute VertexColor')
+
+ -- check vertex itself
+ local x1, y1, u1, v1, r1, g1, b1, a1 = mesh1:getVertex(1)
+ test:assertEquals(0, x1, 'check vertex props x')
+ test:assertEquals(0, y1, 'check vertex props y')
+ test:assertEquals(0, u1, 'check vertex props u')
+ test:assertEquals(0, v1, 'check vertex props v')
+ test:assertEquals(1, r1, 'check vertex props r')
+ test:assertEquals(0, g1, 'check vertex props g')
+ test:assertEquals(0, b1, 'check vertex props b')
+ test:assertEquals(1, a1, 'check vertex props a')
+
+ -- check setting a specific vertex
+ mesh1:setVertex(2, image:getWidth(), 0, 1, 0, 0, 1, 1, 1)
+ local x2, y2, u2, v2, r2, g2, b2, a2 = mesh1:getVertex(2)
+ test:assertEquals(image:getWidth(), x2, 'check changed vertex props x')
+ test:assertEquals(0, y2, 'check changed vertex props y')
+ test:assertEquals(1, u2, 'check changed vertex props u')
+ test:assertEquals(0, v2, 'check changed vertex props v')
+ test:assertEquals(0, r2, 'check changed vertex props r')
+ test:assertEquals(1, g2, 'check changed vertex props g')
+ test:assertEquals(1, b2, 'check changed vertex props b')
+ test:assertEquals(1, a2, 'check changed vertex props a')
+
+ -- check setting a specific vertex attribute
+ local r3, g3, b3, a3 = mesh1:getVertexAttribute(3, 3)
+ test:assertEquals(1, b3, 'check specific vertex color')
+ mesh1:setVertexAttribute(4, 3, 1, 0, 1)
+ local r4, g4, b4, a4 = mesh1:getVertexAttribute(4, 3)
+ test:assertEquals(0, g4, 'check changed vertex color')
+
+ -- check setting a vertice
+ mesh1:setVertices(vertices)
+ local r5, g5, b5, a5 = mesh1:getVertexAttribute(4, 3)
+ local x6, y6, u6, v6, r6, g6, b6, a6 = mesh1:getVertex(2)
+ test:assertEquals(1, g5, 'check reset vertex color 1')
+ test:assertEquals(0, b5, 'check reset vertex color 2')
+
+ -- check setting the vertex map
+ local vmap1 = mesh1:getVertexMap()
+ test:assertEquals(nil, vmap1, 'check no map by def')
+ mesh1:setVertexMap({4, 1, 2, 3})
+ local vmap2 = mesh1:getVertexMap()
+ test:assertEquals(4, #vmap2, 'check set map len')
+ test:assertEquals(2, vmap2[3], 'check set map val')
+
+ -- check using custom attributes
+ local mesh2 = love.graphics.newMesh({
+ { name = 'VertexPosition', format = 'floatvec2'},
+ { name = 'VertexTexCoord', format = 'floatvec2'},
+ { name = 'VertexColor', format = 'floatvec4'},
+ { name = 'CustomValue1', format = 'floatvec2'},
+ { name = 'CustomValue2', format = 'uint16'}
+ }, {
+ { 0, 0, 0, 0, 1, 0, 0, 1, 2, 1, 1005 },
+ { image:getWidth(), 0, 1, 0, 0, 1, 0, 0, 2, 2, 2005 },
+ { image:getWidth(), image:getHeight(), 1, 1, 0, 0, 1, 0, 2, 3, 3005 },
+ { 0, image:getHeight(), 0, 1, 1, 1, 0, 0, 2, 4, 4005 },
+ }, 'fan')
+ local c1, c2 = mesh2:getVertexAttribute(1, 4)
+ local c3 = mesh2:getVertexAttribute(1, 5)
+ test:assertEquals(2, c1, 'check custom attribute val 1')
+ test:assertEquals(1, c2, 'check custom attribute val 2')
+ test:assertEquals(1005, c3, 'check custom attribute val 3')
+
+ -- check attaching custom attribute + detaching
+ mesh1:attachAttribute('CustomValue1', mesh2)
+ test:assertTrue(mesh1:isAttributeEnabled('CustomValue1'), 'check custom attribute attached')
+ mesh1:detachAttribute('CustomValue1')
+ local obj, err = pcall(mesh1.isAttributeEnabled, mesh1, 'CustomValue1')
+ test:assertNotEquals(nil, err, 'check attribute detached')
+ mesh1:detachAttribute('VertexPosition')
+ test:assertTrue(mesh1:isAttributeEnabled('VertexPosition'), 'check cant detach def attribute')
+
+end
+
+
+-- ParticleSystem (love.graphics.newParticleSystem)
+love.test.graphics.ParticleSystem = function(test)
+
+ -- create new system
+ local image = love.graphics.newImage('resources/pixel.png')
+ local quad1 = love.graphics.newQuad(0, 0, 1, 1, image)
+ local quad2 = love.graphics.newQuad(0, 0, 1, 1, image)
+ local psystem = love.graphics.newParticleSystem(image, 1000)
+ test:assertObject(psystem)
+
+ -- check psystem state properties
+ psystem:start()
+ psystem:update(1)
+ test:assertTrue(psystem:isActive(), 'check active')
+ test:assertFalse(psystem:isPaused(), 'checked not paused by def')
+ test:assertFalse(psystem:hasRelativeRotation(), 'check rel rot def')
+ psystem:pause()
+ test:assertTrue(psystem:isPaused(), 'check now paused')
+ test:assertFalse(psystem:isStopped(), 'check not stopped by def')
+ psystem:stop()
+ test:assertTrue(psystem:isStopped(), 'check now stopped')
+ psystem:start()
+ psystem:reset()
+
+ -- check emitting some particles
+ -- need to set a lifespan at minimum or none will be counted
+ local min, max = psystem:getParticleLifetime()
+ test:assertEquals(0, min, 'check def lifetime min')
+ test:assertEquals(0, max, 'check def lifetime max')
+ psystem:setParticleLifetime(1, 2)
+ psystem:emit(10)
+ psystem:update(1)
+ test:assertEquals(10, psystem:getCount(), 'check added particles')
+ psystem:reset()
+ test:assertEquals(0, psystem:getCount(), 'check reset')
+
+ -- check setting colors
+ local colors1 = {psystem:getColors()}
+ test:assertEquals(1, #colors1, 'check 1 color by def')
+ psystem:setColors(1, 1, 1, 1, 1, 0, 0, 1)
+ local colors2 = {psystem:getColors()}
+ test:assertEquals(2, #colors2, 'check set colors')
+ test:assertEquals(1, colors2[2][1], 'check set color')
+
+ -- check setting direction
+ test:assertEquals(0, psystem:getDirection(), 'check def direction')
+ psystem:setDirection(90 * (math.pi/180))
+ test:assertEquals(math.floor(math.pi/2*100), math.floor(psystem:getDirection()*100), 'check set direction')
+
+ -- check emission area options
+ psystem:setEmissionArea('normal', 100, 50)
+ psystem:setEmissionArea('ellipse', 100, 50)
+ psystem:setEmissionArea('borderellipse', 100, 50)
+ psystem:setEmissionArea('borderrectangle', 100, 50)
+ psystem:setEmissionArea('none', 100, 50)
+ psystem:setEmissionArea('uniform', 100, 50)
+ local dist, dx, dy, angle, rel = psystem:getEmissionArea()
+ test:assertEquals('uniform', dist, 'check emission area dist')
+ test:assertEquals(100, dx, 'check emission area dx')
+ test:assertEquals(50, dy, 'check emission area dy')
+ test:assertEquals(0, angle, 'check emission area angle')
+ test:assertFalse(rel, 'check emission area rel')
+
+ -- check emission rate
+ test:assertEquals(0, psystem:getEmissionRate(), 'check def emission rate')
+ psystem:setEmissionRate(1)
+ test:assertEquals(1, psystem:getEmissionRate(), 'check changed emission rate')
+
+ -- check emission lifetime
+ test:assertEquals(-1, psystem:getEmitterLifetime(), 'check def emitter life')
+ psystem:setEmitterLifetime(10)
+ test:assertEquals(10, psystem:getEmitterLifetime(), 'check changed emitter life')
+
+ -- check insert mode
+ test:assertEquals('top', psystem:getInsertMode(), 'check def insert mode')
+ psystem:setInsertMode('bottom')
+ psystem:setInsertMode('random')
+ test:assertEquals('random', psystem:getInsertMode(), 'check change insert mode')
+
+ -- check linear acceleration
+ local xmin1, ymin1, xmax1, ymax1 = psystem:getLinearAcceleration()
+ test:assertEquals(0, xmin1, 'check def lin acceleration xmin')
+ test:assertEquals(0, ymin1, 'check def lin acceleration ymin')
+ test:assertEquals(0, xmax1, 'check def lin acceleration xmax')
+ test:assertEquals(0, ymax1, 'check def lin acceleration ymax')
+ psystem:setLinearAcceleration(1, 2, 3, 4)
+ local xmin2, ymin2, xmax2, ymax2 = psystem:getLinearAcceleration()
+ test:assertEquals(1, xmin2, 'check change lin acceleration xmin')
+ test:assertEquals(2, ymin2, 'check change lin acceleration ymin')
+ test:assertEquals(3, xmax2, 'check change lin acceleration xmax')
+ test:assertEquals(4, ymax2, 'check change lin acceleration ymax')
+
+ -- check linear damping
+ local min3, max3 = psystem:getLinearDamping()
+ test:assertEquals(0, min3, 'check def lin damping min')
+ test:assertEquals(0, max3, 'check def lin damping max')
+ psystem:setLinearDamping(1, 2)
+ local min4, max4 = psystem:getLinearDamping()
+ test:assertEquals(1, min4, 'check change lin damping min')
+ test:assertEquals(2, max4, 'check change lin damping max')
+
+ -- check offset
+ local ox1, oy1 = psystem:getOffset()
+ test:assertEquals(0.5, ox1, 'check def offset x') -- 0.5 cos middle of pixel image which is 1x1
+ test:assertEquals(0.5, oy1, 'check def offset y')
+ psystem:setOffset(0, 10)
+ local ox2, oy2 = psystem:getOffset()
+ test:assertEquals(0, ox2, 'check change offset x')
+ test:assertEquals(10, oy2, 'check change offset y')
+
+ -- check lifetime (we set it earlier)
+ local min5, max5 = psystem:getParticleLifetime()
+ test:assertEquals(1, min5, 'check p lifetime min')
+ test:assertEquals(2, max5, 'check p lifetime max')
+
+ -- check position
+ local x1, y1 = psystem:getPosition()
+ test:assertEquals(0, x1, 'check emitter x')
+ test:assertEquals(0, y1, 'check emitter y')
+ psystem:setPosition(10, 12)
+ local x2, y2 = psystem:getPosition()
+ test:assertEquals(10, x2, 'check set emitter x')
+ test:assertEquals(12, y2, 'check set emitter y')
+
+ -- check quads
+ test:assertEquals(0, #psystem:getQuads(), 'check def quads')
+ psystem:setQuads({quad1})
+ psystem:setQuads(quad1, quad2)
+ test:assertEquals(2, #psystem:getQuads(), 'check set quads')
+
+ -- check radial acceleration
+ local min6, max6 = psystem:getRadialAcceleration()
+ test:assertEquals(0, min6, 'check def rad accel min')
+ test:assertEquals(0, max6, 'check def rad accel max')
+ psystem:setRadialAcceleration(1, 2)
+ local min7, max7 = psystem:getRadialAcceleration()
+ test:assertEquals(1, min7, 'check change rad accel min')
+ test:assertEquals(2, max7, 'check change rad accel max')
+
+ -- check rotation
+ local min8, max8 = psystem:getRotation()
+ test:assertEquals(0, min8, 'check def rot min')
+ test:assertEquals(0, max8, 'check def rot max')
+ psystem:setRotation(90 * (math.pi/180), 180 * (math.pi/180))
+ local min8, max8 = psystem:getRotation()
+ test:assertEquals(math.floor(math.pi/2*100), math.floor(min8*100), 'check set rot min')
+ test:assertEquals(math.floor(math.pi*100), math.floor(max8*100), 'check set rot max')
+
+ -- check variation
+ test:assertEquals(0, psystem:getSizeVariation(), 'check def variation')
+ psystem:setSizeVariation(1)
+ test:assertEquals(1, psystem:getSizeVariation(), 'check change variation')
+
+ -- check sizes
+ test:assertEquals(1, #{psystem:getSizes()}, 'check def size')
+ psystem:setSizes(1, 2, 4, 1, 3, 2)
+ local sizes = {psystem:getSizes()}
+ test:assertEquals(6, #sizes, 'check set sizes')
+ test:assertEquals(3, sizes[5], 'check set size')
+
+ -- check speed
+ local min9, max9 = psystem:getSpeed()
+ test:assertEquals(0, min9, 'check def speed min')
+ test:assertEquals(0, max9, 'check def speed max')
+ psystem:setSpeed(1, 10)
+ local min10, max10 = psystem:getSpeed()
+ test:assertEquals(1, min10, 'check change speed min')
+ test:assertEquals(10, max10, 'check change speed max')
+
+ -- check variation + spin
+ local variation = psystem:getSpinVariation()
+ test:assertEquals(0, variation, 'check def spin variation')
+ psystem:setSpinVariation(1)
+ test:assertEquals(1, psystem:getSpinVariation(), 'check change spin variation')
+ psystem:setSpin(1, 2)
+ local min11, max11 = psystem:getSpin()
+ test:assertEquals(1, min11, 'check change spin min')
+ test:assertEquals(2, max11, 'check change spin max')
+
+ -- check spread
+ test:assertEquals(0, psystem:getSpread(), 'check def spread')
+ psystem:setSpread(90 * (math.pi/180))
+ test:assertEquals(math.floor(math.pi/2*100), math.floor(psystem:getSpread()*100), 'check change spread')
+
+ -- tangential acceleration
+ local min12, max12 = psystem:getTangentialAcceleration()
+ test:assertEquals(0, min12, 'check def tan accel min')
+ test:assertEquals(0, max12, 'check def tan accel max')
+ psystem:setTangentialAcceleration(1, 2)
+ local min13, max13 = psystem:getTangentialAcceleration()
+ test:assertEquals(1, min13, 'check change tan accel min')
+ test:assertEquals(2, max13, 'check change tan accel max')
+
+ -- check texture
+ test:assertNotEquals(nil, psystem:getTexture(), 'check texture obj')
+ test:assertObject(psystem:getTexture())
+ psystem:setTexture(love.graphics.newImage('resources/love.png'))
+ test:assertObject(psystem:getTexture())
+
+ -- try a graphics test!
+ -- hard to get exactly because of the variation but we can use some pixel
+ -- tolerance and volume to try and cover the randomness
+ local psystem2 = love.graphics.newParticleSystem(image, 5000)
+ psystem2:setEmissionArea('uniform', 2, 64)
+ psystem2:setColors(1, 0, 0, 1)
+ psystem2:setDirection(0 * math.pi/180)
+ psystem2:setEmitterLifetime(100)
+ psystem2:setEmissionRate(5000)
+ local psystem3 = psystem2:clone()
+ psystem3:setPosition(64, 0)
+ psystem3:setColors(0, 1, 0, 1)
+ psystem3:setDirection(180 * (math.pi/180))
+ psystem2:start()
+ psystem3:start()
+ psystem2:update(1)
+ psystem3:update(1)
+ local canvas = love.graphics.newCanvas(64, 64)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.draw(psystem2, 0, 0)
+ love.graphics.draw(psystem3, 0, 0)
+ love.graphics.setCanvas()
+ -- this should result in a bunch of red pixels on the left 2px of the canvas
+ -- and a bunch of green pixels on the right 2px of the canvas
+ local imgdata = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test.pixel_tolerance = 1
+ test:compareImg(imgdata)
+
+end
+
+
+-- Quad (love.graphics.newQuad)
+love.test.graphics.Quad = function(test)
+
+ -- create quad obj
+ local texture = love.graphics.newImage('resources/love.png')
+ local quad = love.graphics.newQuad(0, 0, 32, 32, texture)
+ test:assertObject(quad)
+
+ -- check properties
+ test:assertEquals(1, quad:getLayer(), 'check default layer')
+ quad:setLayer(2)
+ test:assertEquals(2, quad:getLayer(), 'check changed layer')
+ local sw, sh = quad:getTextureDimensions()
+ test:assertEquals(64, sw, 'check texture w')
+ test:assertEquals(64, sh, 'check texture h')
+
+ -- check drawing and viewport changes
+ local canvas = love.graphics.newCanvas(64, 64)
+ love.graphics.setCanvas(canvas)
+ love.graphics.draw(texture, quad, 0, 0)
+ quad:setViewport(32, 32, 32, 32, 64, 64)
+ love.graphics.draw(texture, quad, 32, 32)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test:assertPixels(imgdata, {
+ white = {{17,31},{31,31},{31,24},{32,32},{46,32},{32,46}},
+ lovepink = {{2,31},{31,2}},
+ loveblue = {{32,61},{61,32}}
+ }, 'check quad drawing')
+ test:compareImg(imgdata)
+
+end
+
+
+-- Shader (love.graphics.newShader)
+love.test.graphics.Shader = function(test)
+
+ -- check valid shader
+ local pixelcode1 = [[
+ extern Image tex2;
+ vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) {
+ vec4 texturecolor = Texel(tex2, texture_coords);
+ return texturecolor * color;
+ }
+ ]]
+ local vertexcode1 = [[
+ vec4 position(mat4 transform_projection, vec4 vertex_position) {
+ return transform_projection * vertex_position;
+ }
+ ]]
+ local shader1 = love.graphics.newShader(pixelcode1, vertexcode1)
+ test:assertObject(shader1)
+ test:assertEquals('vertex shader:\npixel shader:\n', shader1:getWarnings(), 'check shader valid')
+ test:assertFalse(shader1:hasUniform('tex1'), 'check invalid uniform')
+ test:assertTrue(shader1:hasUniform('tex2'), 'check valid uniform')
+
+ -- check invalid shader
+ local pixelcode2 = [[
+ extern float ww;
+ vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) {
+ vec4 texturecolor = Texel(tex, texture_coords);
+ float unused = ww * 3 * color;
+ return texturecolor * color;
+ }
+ ]]
+ local res, err = pcall(love.graphics.newShader, pixelcode2, vertexcode1)
+ test:assertNotEquals(nil, err, 'check shader compile fails')
+
+ -- check using a shader to draw + sending uniforms
+ -- shader will return a given color if overwrite set to 1, otherwise def. draw
+ local pixelcode3 = [[
+ extern vec4 col;
+ extern float overwrite;
+ vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) {
+ vec4 texcol = Texel(tex, texture_coords);
+ if (overwrite == 1.0) {
+ return col;
+ } else {
+ return texcol * color;
+ }
+ }
+ ]]
+ local shader3 = love.graphics.newShader(pixelcode3, vertexcode1)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ -- set color to yellow
+ love.graphics.setColor(1, 1, 0, 1)
+ -- turn shader 'on' and use red to draw
+ shader3:send('overwrite', 1)
+ shader3:sendColor('col', {1, 0, 0, 1})
+ love.graphics.setShader(shader3)
+ love.graphics.rectangle('fill', 0, 0, 8, 8)
+ love.graphics.setShader()
+ -- turn shader 'off' and draw again
+ shader3:send('overwrite', 0)
+ love.graphics.setShader(shader3)
+ love.graphics.rectangle('fill', 8, 8, 8, 8)
+ love.graphics.setShader()
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{1,1},{1,7},{7,7},{7,1}},
+ yellow = {{8,8},{8,15},{15,15},{15,8}}
+ }, 'shader draw check')
+ test:compareImg(imgdata)
+
+end
+
+
+-- SpriteBatch (love.graphics.newSpriteBatch)
+love.test.graphics.SpriteBatch = function(test)
+
+ -- create batch
+ local texture1 = love.graphics.newImage('resources/cubemap.png')
+ local texture2 = love.graphics.newImage('resources/love.png')
+ local quad1 = love.graphics.newQuad(32, 12, 1, 1, texture2) -- lovepink
+ local quad2 = love.graphics.newQuad(32, 32, 1, 1, texture2) -- white
+ local sbatch = love.graphics.newSpriteBatch(texture1, 5000)
+ test:assertObject(sbatch)
+
+ -- check initial count
+ test:assertEquals(0, sbatch:getCount(), 'check batch size')
+
+ -- check buffer size
+ test:assertEquals(5000, sbatch:getBufferSize(), 'check batch size')
+
+ -- check height/width/texture
+ test:assertEquals(texture1:getWidth(), sbatch:getTexture():getWidth(), 'check texture match w')
+ test:assertEquals(texture1:getHeight(), sbatch:getTexture():getHeight(), 'check texture match h')
+ sbatch:setTexture(texture2)
+ test:assertEquals(texture2:getWidth(), sbatch:getTexture():getWidth(), 'check texture change w')
+ test:assertEquals(texture2:getHeight(), sbatch:getTexture():getHeight(), 'check texture change h')
+
+ -- check colors
+ local r1, g1, b1, a1 = sbatch:getColor()
+ test:assertEquals(1, r1, 'check initial color r')
+ test:assertEquals(1, g1, 'check initial color g')
+ test:assertEquals(1, b1, 'check initial color b')
+ test:assertEquals(1, a1, 'check initial color a')
+ sbatch:setColor(1, 0, 0, 1)
+ local r2, g2, b2, a2 = sbatch:getColor()
+ test:assertEquals(1, r2, 'check set color r')
+ test:assertEquals(0, g2, 'check set color g')
+ test:assertEquals(0, b2, 'check set color b')
+ test:assertEquals(1, a2, 'check set color a')
+
+ -- check adding sprites
+ local offset_x = 0
+ local offset_y = 0
+ local color = 'white'
+ sbatch:setColor(1, 1, 1, 1)
+ local sprites = {}
+ for s=1,4096 do
+ local spr = sbatch:add(quad1, offset_x, offset_y, 0, 1, 1)
+ table.insert(sprites, {spr, offset_x, offset_y})
+ offset_x = offset_x + 1
+ if s % 64 == 0 then
+ -- alternate row colors
+ if color == 'white' then
+ color = 'red'
+ sbatch:setColor(1, 0, 0, 1)
+ else
+ color = 'white'
+ sbatch:setColor(1, 1, 1, 1)
+ end
+ offset_y = offset_y + 1
+ offset_x = 0
+ end
+ end
+ test:assertEquals(4096, sbatch:getCount())
+
+ -- test drawing and setting
+ local canvas = love.graphics.newCanvas(64, 64)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.draw(sbatch, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata1 = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test:assertPixels(imgdata1, {
+ lovepink = {{0,0},{63,2},{0,32},{63,32},{63,0},{63,2}}
+ }, 'sbatch draw normal')
+ test:compareImg(imgdata1)
+
+ -- use set to change some sprites
+ for s=1,2048 do
+ sbatch:set(sprites[s][1], quad2, sprites[s][2], sprites[s][3]+1, 0, 1, 1)
+ end
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.draw(sbatch, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata2 = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test:assertPixels(imgdata2, {
+ lovepink = {{0,32},{63,32}},
+ black = {{0,0},{63,0}},
+ white = {{0,1},{63,1},{0,31},{63,31}}
+ }, 'sbatch draw set')
+ test:compareImg(imgdata2)
+
+ -- set drawRange and redraw
+ sbatch:setDrawRange(1025, 2048)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.draw(sbatch, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata3 = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test:assertPixels(imgdata3, {
+ lovepink = {{0,32},{63,32}},
+ black = {{0,0},{63,0},{0,48},{63,48}},
+ white = {{0,17},{63,17},{0,31},{63,31}}
+ }, 'sbatch draw drawrange')
+ test:compareImg(imgdata3)
+
+ -- clear and redraw
+ sbatch:clear()
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.draw(sbatch, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata4 = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test:assertPixels(imgdata4, {
+ black = {{0,0},{63,0},{0,32},{63,32},{0,63},{63,63}},
+ }, 'sbatch draw clear')
+ test:compareImg(imgdata4)
+
+ -- array texture sbatch
+ local texture3 = love.graphics.newArrayImage({
+ 'resources/love.png',
+ 'resources/loveinv.png'
+ })
+ local asbatch = love.graphics.newSpriteBatch(texture3, 4096)
+ local quad3 = love.graphics.newQuad(32, 52, 1, 1, texture3) -- loveblue
+ sprites = {}
+ for s=1,4096 do
+ local spr = asbatch:addLayer(1, quad3, 0, s, math.floor(s/64), 1, 1)
+ table.insert(sprites, {spr, s, math.floor(s/64)})
+ end
+ test:assertEquals(4096, asbatch:getCount(), 'check max batch size applies')
+ for s=1,2048 do
+ asbatch:setLayer(sprites[s][1], 2, sprites[s][2], sprites[s][3], 0, 1, 1)
+ end
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.draw(asbatch, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata5 = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test:assertPixels(imgdata5, {
+ loveblue = {{31,2},{63,2},{3,30},{3,33},{16,47},{63,47}},
+ lovepink = {{17,48},{63,48},{31,61},{63,61}},
+ black = {{0,0},{63,0},{63,63},{63,0},{30,2},{30,61}},
+ }, 'sbatch draw layers')
+ test:compareImg(imgdata5)
+
+end
+
+
+-- Text (love.graphics.newTextBatch)
+love.test.graphics.Text = function(test)
+
+ -- setup text object
+ local font = love.graphics.newFont('resources/font.ttf', 8)
+ local plaintext = love.graphics.newTextBatch(font, 'test')
+ test:assertObject(plaintext)
+
+ -- check height/width/dimensions
+ test:assertEquals(font:getHeight(), plaintext:getFont():getHeight(), 'check font matches')
+ local tw, th = plaintext:getDimensions()
+ test:assertEquals(24, tw, 'check initial dim w')
+ test:assertEquals(8, th, 'check initial dim h')
+ test:assertEquals(tw, plaintext:getWidth(), 'check initial dim w')
+ test:assertEquals(th, plaintext:getHeight(), 'check initial dim h')
+
+ -- check changing text effects dimensions
+ plaintext:add('more text', 100, 0, 0)
+ test:assertEquals(49, plaintext:getDimensions(), 'check adding text')
+ plaintext:set('test')
+ test:assertEquals(24, plaintext:getDimensions(), 'check resetting text')
+ plaintext:clear()
+ test:assertEquals(0, plaintext:getDimensions(), 'check clearing text')
+
+ -- check drawing + setting more complex text
+ local colortext = love.graphics.newTextBatch(font, {{1, 0, 0, 1}, 'test'})
+ test:assertObject(colortext)
+ colortext:setf('LÖVE is an *awesome* framework you can use to make 2D games in Lua', 60, 'right')
+ colortext:addf({{1, 1, 0}, 'overlap'}, 1000, 'left')
+ local font2 = love.graphics.newFont('resources/font.ttf', 8)
+ colortext:setFont(font2)
+ local canvas = love.graphics.newCanvas(64, 64)
+ love.graphics.setCanvas(canvas)
+ love.graphics.draw(colortext, 0, 10)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ yellow = {{1,9},{8,13},{16,11},{22,10},{25,7},{29,9},{32,13},{34,15}},
+ white = {{17,13},{30,12},{38,9},{44,13},{58,13},{8,29},{58,29},{57,37},{5,39},{57,45},{1,55}}
+ }, 'text draw check')
+ test:compareImg(imgdata)
+
+end
+
+
+-- Video (love.graphics.newVideo)
+love.test.graphics.Video = function(test)
+
+ -- create video obj
+ local video = love.graphics.newVideo('resources/sample.ogv')
+ test:assertObject(video)
+
+ -- check dimensions
+ local w, h = video:getDimensions()
+ test:assertEquals(496, w, 'check vid dim w')
+ test:assertEquals(502, h, 'check vid dim h')
+ test:assertEquals(w, video:getWidth(), 'check vid width match')
+ test:assertEquals(h, video:getHeight(), 'check vid height match')
+
+ -- check filters
+ local min1, mag1, ani1 = video:getFilter()
+ test:assertEquals('nearest', min1, 'check def filter min')
+ test:assertEquals('nearest', mag1, 'check def filter mag')
+ test:assertEquals(1, ani1, 'check def filter ani')
+ video:setFilter('linear', 'linear', 2)
+ local min2, mag2, ani2 = video:getFilter()
+ test:assertEquals('linear', min2, 'check changed filter min')
+ test:assertEquals('linear', mag2, 'check changed filter mag')
+ test:assertEquals(2, ani2, 'check changed filter ani')
+
+ -- check video playing
+ test:assertFalse(video:isPlaying(), 'check paused by default')
+ test:assertEquals(0, video:tell(), 'check 0:00 by default')
+
+ -- covered by their own obj tests in video but check returns obj
+ local source = video:getSource()
+ test:assertObject(source)
+ local stream = video:getStream()
+ test:assertObject(stream)
+
+ -- check playing / pausing / seeking states
+ video:play()
+ test:waitFrames(30) -- 1.5s ish
+ video:pause()
+ test:assertEquals(1, math.ceil(video:tell()), 'check video playing for 1s')
+ video:seek(0.2)
+ test:assertEquals(0.2, video:tell(), 'check video seeking')
+ video:rewind()
+ test:assertEquals(0, video:tell(), 'check video rewind')
+ video:setFilter('nearest', 'nearest', 1)
+
+ -- check actuall drawing with the vid
+ local canvas = love.graphics.newCanvas(500, 500)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(1, 0, 0, 1)
+ love.graphics.draw(video, 0, 0)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {500, 0, 0, 0, 500, 500})
+ test:assertPixels(imgdata, {
+ black = {{0,0},{495,0},{495,499},{0,499}},
+ red = {{499,0},{499,499}}
+ }, 'video draw')
+ test:compareImg(imgdata)
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------DRAWING-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.graphics.arc
+love.test.graphics.arc = function(test)
+ -- draw some arcs using pi format
+ local canvas = love.graphics.newCanvas(32, 32)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.arc('line', "pie", 16, 16, 16, 0 * (math.pi/180), 360 * (math.pi/180), 10)
+ love.graphics.arc('fill', "pie", 16, 16, 16, 270 * (math.pi/180), 45 * (math.pi/180), 10)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.arc('line', "pie", 16, 16, 16, 0 * (math.pi/180), 90 * (math.pi/180), 10)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.arc('line', "pie", 16, 16, 16, 180 * (math.pi/180), 135 * (math.pi/180), 10)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata1 = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ -- draw some arcs with open format
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.arc('line', "open", 16, 16, 16, 0 * (math.pi/180), 315 * (math.pi/180), 10)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.arc('fill', "open", 16, 16, 16, 0 * (math.pi/180), 180 * (math.pi/180), 10)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.arc('fill', "open", 16, 16, 16, 180 * (math.pi/180), 270 * (math.pi/180), 10)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata2 = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ -- draw some arcs with closed format
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.arc('line', "closed", 16, 16, 16, 0 * (math.pi/180), 315 * (math.pi/180), 10)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.arc('fill', "closed", 16, 16, 16, 0 * (math.pi/180), 180 * (math.pi/180), 10)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.arc('line', "closed", 16, 16, 16, 180 * (math.pi/180), 90 * (math.pi/180), 10)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata3 = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ if GITHUB_RUNNER == true and love.system.getOS() == 'OS X' then
+ -- on macosx runners, the arcs are not drawn as accurately at low res
+ -- there's a couple pixels different in the curve of the arc but as we
+ -- are at such a low resolution I think that can be expected
+ -- on real hardware the test passes fine though
+ test:assertTrue(true, 'skip test')
+ else
+ test:compareImg(imgdata1)
+ test:compareImg(imgdata2)
+ test:compareImg(imgdata3)
+ end
+end
+
+
+-- love.graphics.circle
+love.test.graphics.circle = function(test)
+ -- draw some circles
+ local canvas = love.graphics.newCanvas(32, 32)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.circle('fill', 16, 16, 16)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.circle('line', 16, 16, 16)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.circle('fill', 16, 16, 8)
+ love.graphics.setColor(0, 1, 0, 1)
+ love.graphics.circle('fill', 16, 16, 4)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ white = {{13,8},{18,8},{23,13},{23,18}},
+ green = {
+ {15,12},{16,12},{13,13},{18,13},{12,15},{12,16},{13,18},{18,18},
+ {15,19},{16,19},{19,15},{19,16}
+ },
+ black = {{10,0},{21,0},{0,10},{0,21},{31,10},{31,21},{10,31},{21,31}},
+ yellow = {
+ {11,10},{10,11},{8,14},{8,17},{10,20},{11,21},{14,23},{17,23},{20,21},
+ {21,20},{23,17},{23,14},{20,10},{21,11},{17,8},{14,8}
+ },
+ red = {{11,0},{20,0},{11,31},{20,31},{0,11},{0,20},{31,20},{31,11}}
+ }, 'circle')
+ test:compareImg(imgdata)
+end
+
+
+
+-- love.graphics.clear
+love.test.graphics.clear = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.clear(1, 1, 0, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ yellow = {{0,0},{15,0},{0,15},{15,15},{8,8}}
+ }, 'clear')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.discard
+love.test.graphics.discard = function(test)
+ -- from the docs: "on some desktops this may do nothing"
+ test:skipTest('cant test this worked')
+end
+
+
+-- love.graphics.draw
+love.test.graphics.draw = function(test)
+ local canvas1 = love.graphics.newCanvas(32, 32)
+ local canvas2 = love.graphics.newCanvas(32, 32)
+ local transform = love.math.newTransform( )
+ transform:translate(16, 0)
+ transform:scale(0.5, 0.5)
+ love.graphics.setCanvas(canvas1)
+ love.graphics.clear(0, 0, 0, 1)
+ -- img, offset
+ love.graphics.draw(Logo.texture, Logo.img, 0, 0, 0, 1, 1, 16, 16)
+ love.graphics.setCanvas()
+ love.graphics.setCanvas(canvas2)
+ love.graphics.clear(1, 0, 0, 1)
+ -- canvas, scale, shear, transform obj
+ love.graphics.draw(canvas1, 0, 0, 0, 1, 1, 0, 0, 2, 2)
+ love.graphics.draw(canvas1, 0, 16, 0, 0.5, 0.5)
+ love.graphics.draw(canvas1, 16, 16, 0, 0.5, 0.5)
+ love.graphics.draw(canvas1, transform)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas2, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ lovepink = {{23,3},{23,19},{7,19},{0,0},{16,0},{0,16},{16,16}},
+ loveblue = {{0,31},{15,17},{15,31},{16,31},{31,17},{31,31},{16,15},{31,15}},
+ white = {{6,19},{8,19},{22,19},{24,19},{22,3},{24,3}},
+ red = {{0,1},{1,0},{15,0},{15,7},{0,15},{7,15}}
+ }, 'drawing')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.drawInstanced
+love.test.graphics.drawInstanced = function(test)
+ local image = love.graphics.newImage('resources/love.png')
+ local vertices = {
+ { 0, 0, 0, 0, 1, 0, 0 },
+ { image:getWidth(), 0, 1, 0, 0, 1, 0 },
+ { image:getWidth(), image:getHeight(), 1, 1, 0, 0, 1 },
+ { 0, image:getHeight(), 0, 1, 1, 1, 0 },
+ }
+ local mesh = love.graphics.newMesh(vertices, 'fan')
+ local canvas = love.graphics.newCanvas(64, 64)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.drawInstanced(mesh, 1000, 0, 0, 0, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {64, 0, 0, 0, 64, 64})
+ test:assertPixels(imgdata, {
+ red = {{0,0}},
+ green = {{63,0}},
+ blue = {{63,63}},
+ yellow = {{0,63}}
+ }, 'draw instances')
+ -- need 1 tolerance here just cos of the amount of colors
+ test.rgba_tolerance = 1
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.drawLayer
+love.test.graphics.drawLayer = function(test)
+ local image = love.graphics.newArrayImage({
+ 'resources/love.png', 'resources/loveinv.png',
+ 'resources/love.png', 'resources/loveinv.png'
+ })
+ local canvas = love.graphics.newCanvas(64, 64)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.drawLayer(image, 1, 0, 0, 0, 1, 1)
+ love.graphics.drawLayer(image, 2, 32, 0, 0, 0.5, 0.5)
+ love.graphics.drawLayer(image, 4, 0, 32, 0, 0.5, 0.5)
+ love.graphics.drawLayer(image, 3, 32, 32, 0, 2, 2, 16, 16)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ lovepink = {{30,2},{33,2},{2,30},{2,33},{4,60},{4,63},{60,4},{63,4},{31,23},{32,23}},
+ loveblue = {{14,33},{17,33},{46,1},{49,1},{1,46},{1,49},{33,14},{33,17}},
+ black = {{0,0},{63,0},{0,63},{39,6},{40,6},{6,39},{6,40},{6,55},{55,6}},
+ white = {{46,11},{48,11},{14,43},{16,43},{30,23},{33,23},{34,54},{53,40},{63,63}}
+ }, 'draw layer')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.ellipse
+love.test.graphics.ellipse = function(test)
+ local canvas = love.graphics.newCanvas(32, 32)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.ellipse('fill', 16, 16, 16, 8)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.ellipse('fill', 24, 24, 10, 24)
+ love.graphics.setColor(1, 0, 1, 1)
+ love.graphics.ellipse('fill', 16, 0, 8, 16)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{0,14},{0,17},{7,9},{7,22},{14,15},{14,16}},
+ pink = {{15,15},{16,15},{8,0},{8,4},{23,0},{23,4},{13,14},{18,14}},
+ yellow = {{24,0},{25,0},{14,17},{14,30},{15,31},{31,8}}
+ }, 'ellipses')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.flushBatch
+love.test.graphics.flushBatch = function(test)
+ love.graphics.flushBatch()
+ local initial = love.graphics.getStats()['drawcalls']
+ local canvas = love.graphics.newCanvas(32, 32)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 32, 32)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ love.graphics.flushBatch()
+ local after = love.graphics.getStats()['drawcalls']
+ test:assertEquals(initial+1, after, 'check drawcalls increased')
+end
+
+
+-- love.graphics.line
+love.test.graphics.line = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.line(1,1,16,1,16,16,1,16,1,1)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.line({0,0,8,8,16,0,8,8,16,16,8,8,0,16})
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ yellow = {{0,0},{15,0},{0,15},{15,15},{7,7},{8,7},{8,7},{8,8}},
+ red = {{1,0},{14,0},{0,1},{0,14},{15,1},{15,14},{1,15},{14,15}}
+ }, 'lines')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.points
+love.test.graphics.points = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.points(1,1,16,1,16,16,1,16,1,1)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.points({2,2,8,8,15,2,8,9,15,15,9,9,2,15,9,8})
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ -- on macOS runners points are drawn 1px off from the target
+ if GITHUB_RUNNER == true and love.system.getOS() == 'OS X' then
+ test.pixel_tolerance = 1
+ end
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.polygon
+love.test.graphics.polygon = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.polygon("fill", 1, 1, 4, 5, 8, 10, 16, 2, 7, 3, 5, 16, 16, 16, 1, 8)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.polygon("line", {2, 2, 4, 5, 3, 7, 8, 15, 12, 4, 5, 10})
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ yellow = {{1,0},{1,1},{5,9},{7,14},{8,14},{12,3}},
+ red = {{2,1},{1,2},{1,7},{5,15},{14,15},{8,8},{14,2},{7,1}}
+ }, 'polygon')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.print
+love.test.graphics.print = function(test)
+ love.graphics.setFont(Font)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.print('love', 0, 3, 0, 1, 1, 0, 0)
+ love.graphics.setColor(0, 1, 0, 1)
+ love.graphics.print('ooo', 0, 3, 0, 2, 2, 0, 0)
+ love.graphics.setColor(0, 0, 1, 1)
+ love.graphics.print('hello', 0, 3, 90*(math.pi/180), 1, 1, 0, 8)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{0,0},{1,0},{1,1},{2,6},{4,4},{7,6},{10,2},{11,5},{14,3},{14,4}},
+ green = {
+ {2,1},{2,2},{0,3},{1,3},{1,8},{2,9},{7,10},{8,8},{9,4},{13,3},{14,2},
+ {13,8},{14,9}
+ },
+ blue = {
+ {4,15},{10,15},{4,12},{6,12},{8,12},{5,9},{7,9},{4,3},{10,3},{8,6},{7,7},
+ {4,7},{7,13},{8,12}
+ }
+ }, 'print')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.printf
+love.test.graphics.printf = function(test)
+ love.graphics.setFont(Font)
+ local canvas = love.graphics.newCanvas(32, 32)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.printf('love', 0, 0, 8, "left")
+ love.graphics.setColor(0, 1, 0, 1)
+ love.graphics.printf('love', 0, 5, 16, "right")
+ love.graphics.setColor(0, 0, 1, 1)
+ love.graphics.printf('love', 0, 7, 32, "center")
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {
+ {1,0},{1,1},{0,3},{2,3},{2,7},{0,9},{3,11},{4,10},{0,15},{4,15},{2,19},
+ {0,24},{1,23},{3,23},{4,24},{0,26},{1,27},{2,27},{3,27}
+ },
+ green = {
+ {1,2},{0,8},{1,8},{2,8},{4,7},{5,8},{7,8},{8,7},{10,4},{14,4},{11,7},
+ {12,8},{10,13},{11,12},{13,12},{14,13},{10,15},{11,16}
+ },
+ blue = {{6,4},{6,10},{9,7},{10,6},{16,9},{18,9},{21,8},{25,8}}
+ }, 'printf')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.rectangle
+love.test.graphics.rectangle = function(test)
+ -- setup, draw a 16x16 red rectangle with a blue central square
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 16, 16)
+ love.graphics.setColor(0, 0, 1, 1)
+ love.graphics.rectangle('fill', 6, 6, 4, 4)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata1 = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ -- test, check red bg and blue central square
+ test:assertPixels(imgdata1, {
+ red = {{0,0},{15,0},{15,15},{0,15}},
+ blue = {{6,6},{9,6},{9,9},{6,9}}
+ }, 'fill')
+ -- clear canvas to do some line testing
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('line', 1, 1, 15, 15) -- red border
+ love.graphics.setColor(0, 0, 1, 1)
+ love.graphics.rectangle('line', 1, 1, 2, 15) -- 3x16 left aligned blue outline
+ love.graphics.setColor(0, 1, 0, 1)
+ love.graphics.rectangle('line', 11, 1, 5, 15) -- 6x16 right aligned green outline
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata2 = love.graphics.readbackTexture(canvas, {1, 1, 0, 0, 16, 16})
+ -- -- check corners and inner corners
+ test:assertPixels(imgdata2, {
+ red = {{3,0},{9,0},{3,15,9,15}},
+ blue = {{0,0},{2,0},{0,15},{2,15}},
+ green = {{10,0},{15,0},{10,15},{15,15}},
+ black = {
+ {1,1},{1,14},{3,1},{9,1},{3,14},
+ {9,14},{11,1},{14,1},{11,14},{14,14}
+ }
+ }, 'line')
+ test:compareImg(imgdata1)
+ test:compareImg(imgdata2)
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------OBJECT CREATION---------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.graphics.captureScreenshot
+love.test.graphics.captureScreenshot = function(test)
+ love.graphics.captureScreenshot('example-screenshot.png')
+ test:waitFrames(10)
+ -- need to wait until end of the frame for the screenshot
+ test:assertNotNil(love.filesystem.openFile('example-screenshot.png', 'r'))
+ love.filesystem.remove('example-screenshot.png')
+end
+
+
+-- love.graphics.newArrayImage
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newArrayImage = function(test)
+ test:assertObject(love.graphics.newArrayImage({
+ 'resources/love.png', 'resources/love2.png', 'resources/love3.png'
+ }))
+end
+
+-- love.graphics.newCanvas
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newCanvas = function(test)
+ test:assertObject(love.graphics.newCanvas(16, 16, {
+ type = '2d',
+ format = 'normal',
+ readable = true,
+ msaa = 0,
+ dpiscale = 1,
+ mipmaps = 'none'
+ }))
+ test:assertObject(love.graphics.newCanvas(1000, 1000))
+end
+
+
+-- love.graphics.newCubeImage
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newCubeImage = function(test)
+ test:assertObject(love.graphics.newCubeImage('resources/cubemap.png', {
+ mipmaps = false,
+ linear = false
+ }))
+end
+
+
+-- love.graphics.newFont
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newFont = function(test)
+ test:assertObject(love.graphics.newFont('resources/font.ttf'))
+ test:assertObject(love.graphics.newFont('resources/font.ttf', 8, "normal", 1))
+end
+
+
+-- love.graphics.newImage
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newImage = function(test)
+ test:assertObject(love.graphics.newImage('resources/love.png', {
+ mipmaps = false,
+ linear = false,
+ dpiscale = 1
+ }))
+end
+
+
+-- love.graphics.newImageFont
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newImageFont = function(test)
+ test:assertObject(love.graphics.newImageFont('resources/love.png', 'ABCD', 1))
+end
+
+
+-- love.graphics.newMesh
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newMesh = function(test)
+ test:assertObject(love.graphics.newMesh({{1, 1, 0, 0, 1, 1, 1, 1}}, 'fan', 'dynamic'))
+end
+
+
+-- love.graphics.newParticleSystem
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newParticleSystem = function(test)
+ local imgdata = love.graphics.newImage('resources/love.png')
+ test:assertObject(love.graphics.newParticleSystem(imgdata, 1000))
+end
+
+
+-- love.graphics.newQuad
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newQuad = function(test)
+ local imgdata = love.graphics.newImage('resources/love.png')
+ test:assertObject(love.graphics.newQuad(0, 0, 16, 16, imgdata))
+end
+
+
+-- love.graphics.newShader
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newShader = function(test)
+ local pixelcode = [[
+ vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) {
+ vec4 texturecolor = Texel(tex, texture_coords);
+ return texturecolor * color;
+ }
+ ]]
+ local vertexcode = [[
+ vec4 position(mat4 transform_projection, vec4 vertex_position) {
+ return transform_projection * vertex_position;
+ }
+ ]]
+ test:assertObject(love.graphics.newShader(pixelcode, vertexcode))
+end
+
+
+-- love.graphics.newSpriteBatch
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newSpriteBatch = function(test)
+ local imgdata = love.graphics.newImage('resources/love.png')
+ test:assertObject(love.graphics.newSpriteBatch(imgdata, 1000))
+end
+
+
+-- love.graphics.newText
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newTextBatch = function(test)
+ local font = love.graphics.newFont('resources/font.ttf')
+ test:assertObject(love.graphics.newTextBatch(font, 'helloworld'))
+end
+
+
+-- love.graphics.newVideo
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newVideo = function(test)
+ test:assertObject(love.graphics.newVideo('resources/sample.ogv', {
+ audio = false,
+ dpiscale = 1
+ }))
+end
+
+
+-- love.graphics.newVolumeImage
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.graphics.newVolumeImage = function(test)
+ test:assertObject(love.graphics.newVolumeImage({
+ 'resources/love.png', 'resources/love2.png', 'resources/love3.png'
+ }, {
+ mipmaps = false,
+ linear = false
+ }))
+end
+
+
+-- love.graphics.validateShader
+love.test.graphics.validateShader = function(test)
+ local pixelcode = [[
+ vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) {
+ vec4 texturecolor = Texel(tex, texture_coords);
+ return texturecolor * color;
+ }
+ ]]
+ local vertexcode = [[
+ vec4 position(mat4 transform_projection, vec4 vertex_position) {
+ return transform_projection * vertex_position;
+ }
+ ]]
+ -- check made up code first
+ local status, _ = love.graphics.validateShader(true, 'nothing here', 'or here')
+ test:assertFalse(status, 'check invalid shader code')
+ -- check real code
+ status, _ = love.graphics.validateShader(true, pixelcode, vertexcode)
+ test:assertTrue(status, 'check valid shader code')
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+---------------------------------GRAPHICS STATE---------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.graphics.getBackgroundColor
+love.test.graphics.getBackgroundColor = function(test)
+ -- check default bg is black
+ local r, g, b, a = love.graphics.getBackgroundColor()
+ test:assertEquals(0, r, 'check default background r')
+ test:assertEquals(0, g, 'check default background g')
+ test:assertEquals(0, b, 'check default background b')
+ test:assertEquals(1, a, 'check default background a')
+ -- check set value returns correctly
+ love.graphics.setBackgroundColor(1, 1, 1, 0)
+ r, g, b, a = love.graphics.getBackgroundColor()
+ test:assertEquals(1, r, 'check updated background r')
+ test:assertEquals(1, g, 'check updated background g')
+ test:assertEquals(1, b, 'check updated background b')
+ test:assertEquals(0, a, 'check updated background a')
+ love.graphics.setBackgroundColor(0, 0, 0, 1) -- reset
+end
+
+
+-- love.graphics.getBlendMode
+love.test.graphics.getBlendMode = function(test)
+ -- check default blend mode
+ local mode, alphamode = love.graphics.getBlendMode()
+ test:assertEquals('alpha', mode, 'check default blend mode')
+ test:assertEquals('alphamultiply', alphamode, 'check default alpha blend')
+ -- check set mode returns correctly
+ love.graphics.setBlendMode('add', 'premultiplied')
+ mode, alphamode = love.graphics.getBlendMode()
+ test:assertEquals('add', mode, 'check changed blend mode')
+ test:assertEquals('premultiplied', alphamode, 'check changed alpha blend')
+ love.graphics.setBlendMode('alpha', 'alphamultiply') -- reset
+end
+
+
+-- love.graphics.getCanvas
+love.test.graphics.getCanvas = function(test)
+ -- by default should be nil if drawing to real screen
+ test:assertEquals(nil, love.graphics.getCanvas(), 'check no canvas set')
+ -- should return not nil when we target a canvas
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ test:assertObject(love.graphics.getCanvas())
+ love.graphics.setCanvas()
+end
+
+
+-- love.graphics.getColor
+love.test.graphics.getColor = function(test)
+ -- by default should be white
+ local r, g, b, a = love.graphics.getColor()
+ test:assertEquals(1, r, 'check default color r')
+ test:assertEquals(1, g, 'check default color g')
+ test:assertEquals(1, b, 'check default color b')
+ test:assertEquals(1, a, 'check default color a')
+ -- check set color is returned correctly
+ love.graphics.setColor(0, 0, 0, 0)
+ r, g, b, a = love.graphics.getColor()
+ test:assertEquals(0, r, 'check changed color r')
+ test:assertEquals(0, g, 'check changed color g')
+ test:assertEquals(0, b, 'check changed color b')
+ test:assertEquals(0, a, 'check changed color a')
+ love.graphics.setColor(1, 1, 1, 1) -- reset
+end
+
+
+-- love.graphics.getColorMask
+love.test.graphics.getColorMask = function(test)
+ -- by default should all be active
+ local r, g, b, a = love.graphics.getColorMask()
+ test:assertTrue(r, 'check default color mask r')
+ test:assertTrue(g, 'check default color mask g')
+ test:assertTrue(b, 'check default color mask b')
+ test:assertTrue(a, 'check default color mask a')
+ -- check set color mask is returned correctly
+ love.graphics.setColorMask(false, false, true, false)
+ r, g, b, a = love.graphics.getColorMask()
+ test:assertFalse(r, 'check changed color mask r')
+ test:assertFalse(g, 'check changed color mask g')
+ test:assertTrue( b, 'check changed color mask b')
+ test:assertFalse(a, 'check changed color mask a')
+ love.graphics.setColorMask(true, true, true, true) -- reset
+end
+
+
+-- love.graphics.getDefaultFilter
+love.test.graphics.getDefaultFilter = function(test)
+ -- we set this already for testsuite so we know what it should be
+ local min, mag, anisotropy = love.graphics.getDefaultFilter()
+ test:assertEquals('nearest', min, 'check default filter min')
+ test:assertEquals('nearest', mag, 'check default filter mag')
+ test:assertEquals(1, anisotropy, 'check default filter mag')
+end
+
+
+-- love.graphics.getDepthMode
+love.test.graphics.getDepthMode = function(test)
+ -- by default should be always/write
+ local comparemode, write = love.graphics.getDepthMode()
+ test:assertEquals('always', comparemode, 'check default compare depth')
+ test:assertFalse(write, 'check default depth buffer write')
+end
+
+
+-- love.graphics.getFont
+love.test.graphics.getFont = function(test)
+ test:assertObject(love.graphics.getFont())
+end
+
+
+-- love.graphics.getFrontFaceWinding
+love.test.graphics.getFrontFaceWinding = function(test)
+ -- check default winding
+ test:assertEquals('ccw', love.graphics.getFrontFaceWinding())
+ -- check setting value changes it correctly
+ love.graphics.setFrontFaceWinding('cw')
+ test:assertEquals('cw', love.graphics.getFrontFaceWinding())
+ love.graphics.setFrontFaceWinding('ccw') -- reset
+end
+
+
+-- love.graphics.getLineJoin
+love.test.graphics.getLineJoin = function(test)
+ -- check default line join
+ test:assertEquals('miter', love.graphics.getLineJoin())
+ -- check set value returned correctly
+ love.graphics.setLineJoin('none')
+ test:assertEquals('none', love.graphics.getLineJoin())
+ love.graphics.setLineJoin('miter') -- reset
+end
+
+
+-- love.graphics.getLineStyle
+love.test.graphics.getLineStyle = function(test)
+ -- we know this should be as testsuite sets it!
+ test:assertEquals('rough', love.graphics.getLineStyle())
+ -- check set value returned correctly
+ love.graphics.setLineStyle('smooth')
+ test:assertEquals('smooth', love.graphics.getLineStyle())
+ love.graphics.setLineStyle('rough') -- reset
+end
+
+
+-- love.graphics.getLineWidth
+love.test.graphics.getLineWidth = function(test)
+ -- we know this should be as testsuite sets it!
+ test:assertEquals(1, love.graphics.getLineWidth())
+ -- check set value returned correctly
+ love.graphics.setLineWidth(10)
+ test:assertEquals(10, love.graphics.getLineWidth())
+ love.graphics.setLineWidth(1) -- reset
+end
+
+
+-- love.graphics.getMeshCullMode
+love.test.graphics.getMeshCullMode = function(test)
+ -- get default mesh culling
+ test:assertEquals('none', love.graphics.getMeshCullMode())
+ -- check set value returned correctly
+ love.graphics.setMeshCullMode('front')
+ test:assertEquals('front', love.graphics.getMeshCullMode())
+ love.graphics.setMeshCullMode('back') -- reset
+end
+
+
+-- love.graphics.getPointSize
+love.test.graphics.getPointSize = function(test)
+ -- get default point size
+ test:assertEquals(1, love.graphics.getPointSize())
+ -- check set value returned correctly
+ love.graphics.setPointSize(10)
+ test:assertEquals(10, love.graphics.getPointSize())
+ love.graphics.setPointSize(1) -- reset
+end
+
+
+-- love.graphics.getScissor
+love.test.graphics.getScissor = function(test)
+ -- should be no scissor atm
+ local x, y, w, h = love.graphics.getScissor()
+ test:assertEquals(nil, x, 'check no scissor')
+ test:assertEquals(nil, y, 'check no scissor')
+ test:assertEquals(nil, w, 'check no scissor')
+ test:assertEquals(nil, h, 'check no scissor')
+ -- check set value returned correctly
+ love.graphics.setScissor(0, 0, 16, 16)
+ x, y, w, h = love.graphics.getScissor()
+ test:assertEquals(0, x, 'check scissor set')
+ test:assertEquals(0, y, 'check scissor set')
+ test:assertEquals(16, w, 'check scissor set')
+ test:assertEquals(16, h, 'check scissor set')
+ love.graphics.setScissor() -- reset
+end
+
+
+-- love.graphics.getShader
+love.test.graphics.getShader = function(test)
+ -- should be no shader active
+ test:assertEquals(nil, love.graphics.getShader(), 'check no active shader')
+end
+
+
+-- love.graphics.getStackDepth
+love.test.graphics.getStackDepth = function(test)
+ -- by default should be none
+ test:assertEquals(0, love.graphics.getStackDepth(), 'check no transforms in stack')
+ -- now add 3
+ love.graphics.push()
+ love.graphics.push()
+ love.graphics.push()
+ test:assertEquals(3, love.graphics.getStackDepth(), 'check 3 transforms in stack')
+ -- now remove 2
+ love.graphics.pop()
+ love.graphics.pop()
+ test:assertEquals(1, love.graphics.getStackDepth(), 'check 1 transforms in stack')
+ -- now back to 0
+ love.graphics.pop()
+ test:assertEquals(0, love.graphics.getStackDepth(), 'check no transforms in stack')
+end
+
+
+-- love.graphics.getStencilMode
+love.test.graphics.getStencilMode = function(test)
+ -- check default vals
+ local action, comparemode, value = love.graphics.getStencilMode( )
+ test:assertEquals('keep', action, 'check default stencil action')
+ test:assertEquals('always', comparemode, 'check default stencil compare')
+ test:assertEquals(0, value, 'check default stencil value')
+ -- check set stencil values is returned
+ love.graphics.setStencilMode('replace', 'less', 255)
+ local action, comparemode, value = love.graphics.getStencilMode()
+ test:assertEquals('replace', action, 'check changed stencil action')
+ test:assertEquals('less', comparemode, 'check changed stencil compare')
+ test:assertEquals(255, value, 'check changed stencil value')
+ love.graphics.setStencilMode() -- reset
+end
+
+
+-- love.graphics.intersectScissor
+love.test.graphics.intersectScissor = function(test)
+ -- make a scissor for the left half, then interset to make the top half
+ -- then we should be able to fill the canvas with red and only top 4x4 is filled
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.origin()
+ love.graphics.setScissor(0, 0, 8, 16)
+ love.graphics.intersectScissor(0, 0, 4, 4)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 16, 16)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setScissor()
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{0,0},{3,3}},
+ black ={{4,0},{0,4},{4,4}}
+ }, 'intersect scissor')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.isActive
+love.test.graphics.isActive = function(test)
+ local name, version, vendor, device = love.graphics.getRendererInfo()
+ if string.find(name, 'Vulkan') ~= nil then
+ test:skipTest('love.graphics.isActive() crashes on Vulkan')
+ else
+ test:assertTrue(love.graphics.isActive(), 'check graphics is active') -- i mean if you got this far
+ end
+end
+
+
+-- love.graphics.isGammaCorrect
+love.test.graphics.isGammaCorrect = function(test)
+ -- we know the config so know this is false
+ test:assertNotNil(love.graphics.isGammaCorrect())
+end
+
+
+-- love.graphics.isWireframe
+love.test.graphics.isWireframe = function(test)
+ local name, version, vendor, device = love.graphics.getRendererInfo()
+ if string.match(name, 'OpenGL ES') then
+ test:skipTest('Wireframe not supported on OpenGL ES')
+ else
+ -- check off by default
+ test:assertFalse(love.graphics.isWireframe(), 'check no wireframe by default')
+ -- check on when enabled
+ love.graphics.setWireframe(true)
+ test:assertTrue(love.graphics.isWireframe(), 'check wireframe is set')
+ love.graphics.setWireframe(false) -- reset
+ end
+end
+
+
+-- love.graphics.reset
+love.test.graphics.reset = function(test)
+ -- reset should reset current canvas and any colors/scissor
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setBackgroundColor(0, 0, 1, 1)
+ love.graphics.setColor(0, 1, 0, 1)
+ love.graphics.setCanvas(canvas)
+ love.graphics.reset()
+ local r, g, b, a = love.graphics.getBackgroundColor()
+ test:assertEquals(1, r+g+b+a, 'check background reset')
+ r, g, b, a = love.graphics.getColor()
+ test:assertEquals(4, r+g+b+a, 'check color reset')
+ test:assertEquals(nil, love.graphics.getCanvas(), 'check canvas reset')
+ love.graphics.setDefaultFilter("nearest", "nearest")
+ love.graphics.setLineStyle('rough')
+ love.graphics.setPointSize(1)
+ love.graphics.setLineWidth(1)
+end
+
+
+-- love.graphics.setBackgroundColor
+love.test.graphics.setBackgroundColor = function(test)
+ -- check background is set
+ love.graphics.setBackgroundColor(1, 0, 0, 1)
+ local r, g, b, a = love.graphics.getBackgroundColor()
+ test:assertEquals(1, r, 'check set bg r')
+ test:assertEquals(0, g, 'check set bg g')
+ test:assertEquals(0, b, 'check set bg b')
+ test:assertEquals(1, a, 'check set bg a')
+ love.graphics.setBackgroundColor(0, 0, 0, 1)
+end
+
+
+-- love.graphics.setBlendMode
+love.test.graphics.setBlendMode = function(test)
+ -- create fully white canvas, then draw diff. pixels through blendmodes
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0.5, 0.5, 0.5, 1)
+ love.graphics.setBlendMode('add', 'alphamultiply')
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.setBlendMode('subtract', 'alphamultiply')
+ love.graphics.setColor(1, 1, 1, 0.5)
+ love.graphics.rectangle('fill', 15, 0, 1, 1)
+ love.graphics.setBlendMode('multiply', 'premultiplied')
+ love.graphics.setColor(0, 1, 0, 1)
+ love.graphics.rectangle('fill', 15, 15, 1, 1)
+ love.graphics.setBlendMode('replace', 'premultiplied')
+ love.graphics.setColor(0, 0, 1, 0.5)
+ love.graphics.rectangle('fill', 0, 15, 1, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ -- check the 4 corners
+ test:assertPixels(imgdata, {
+ redpale = {{0,0}},
+ black = {{15,0}},
+ greenhalf = {{15,15}},
+ bluefade = {{0,15}}
+ }, 'blend mode')
+ love.graphics.setBlendMode('alpha', 'alphamultiply') -- reset
+ -- need 1rgba tolerance here on some machines
+ test.rgba_tolerance = 1
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setCanvas
+love.test.graphics.setCanvas = function(test)
+ -- make 2 canvas, set to each, draw one to the other, check output
+ local canvas1 = love.graphics.newCanvas(16, 16)
+ local canvas2 = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas1)
+ test:assertEquals(canvas1, love.graphics.getCanvas(), 'check canvas 1 set')
+ love.graphics.clear(1, 0, 0, 1)
+ love.graphics.setCanvas(canvas2)
+ test:assertEquals(canvas2, love.graphics.getCanvas(), 'check canvas 2 set')
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.draw(canvas1, 0, 0)
+ love.graphics.setCanvas()
+ test:assertEquals(nil, love.graphics.getCanvas(), 'check no canvas set')
+ local imgdata = love.graphics.readbackTexture(canvas2, {16, 0, 0, 0, 16, 16})
+ -- check 2nd canvas is red
+ test:assertPixels(imgdata, {
+ red = {{0,0},{15,0},{15,15},{0,15}}
+ }, 'set canvas')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setColor
+love.test.graphics.setColor = function(test)
+ -- set colors, draw rect, check color
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ local r, g, b, a = love.graphics.getColor()
+ test:assertEquals(1, r, 'check r set')
+ test:assertEquals(0, g, 'check g set')
+ test:assertEquals(0, b, 'check b set')
+ test:assertEquals(1, a, 'check a set')
+
+ love.graphics.rectangle('fill', 0, 0, 16, 1)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.rectangle('fill', 0, 1, 16, 1)
+ love.graphics.setColor(0, 1, 0, 0.5)
+ love.graphics.rectangle('fill', 0, 2, 16, 1)
+ love.graphics.setColor(0, 0, 1, 1)
+ love.graphics.rectangle('fill', 0, 3, 16, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{0,0},{5,0},{10,0},{15,0}},
+ yellow = {{0,1},{5,1},{10,1},{15,1}},
+ greenhalf = {{0,2},{5,2},{10,2},{15,2}},
+ blue = {{0,3},{5,3},{10,3},{15,3}}
+ }, 'set color')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setColorMask
+love.test.graphics.setColorMask = function(test)
+ -- set mask, draw stuff, check output pixels
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ -- mask off blue
+ love.graphics.setColorMask(true, true, false, true)
+ local r, g, b, a = love.graphics.getColorMask()
+ test:assertEquals(r, true, 'check r mask')
+ test:assertEquals(g, true, 'check g mask')
+ test:assertEquals(b, false, 'check b mask')
+ test:assertEquals(a, true, 'check a mask')
+ -- draw "black" which should then turn to yellow
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.rectangle('fill', 0, 0, 16, 16)
+ love.graphics.setColorMask(true, true, true, true)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ yellow = {{0,0},{0,15},{15,15},{15,0}}
+ }, 'set color mask')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setDefaultFilter
+love.test.graphics.setDefaultFilter = function(test)
+ -- check setting filter val works
+ love.graphics.setDefaultFilter('linear', 'linear', 1)
+ local min, mag, anisotropy = love.graphics.getDefaultFilter()
+ test:assertEquals('linear', min, 'check default filter min')
+ test:assertEquals('linear', mag, 'check default filter mag')
+ test:assertEquals(1, anisotropy, 'check default filter mag')
+ love.graphics.setDefaultFilter('nearest', 'nearest', 1) -- reset
+end
+
+
+-- love.graphics.setDepthMode
+love.test.graphics.setDepthMode = function(test)
+ -- check documented modes are valid
+ local comparemode, write = love.graphics.getDepthMode()
+ local modes = {
+ 'equal', 'notequal', 'less', 'lequal', 'gequal',
+ 'greater', 'never', 'always'
+ }
+ for m=1,#modes do
+ love.graphics.setDepthMode(modes[m], true)
+ test:assertEquals(modes[m], love.graphics.getDepthMode(), 'check depth mode ' .. modes[m] .. ' set')
+ end
+ love.graphics.setDepthMode(comparemode, write)
+ -- @TODO better graphics drawing specific test
+end
+
+
+-- love.graphics.setFont
+love.test.graphics.setFont = function(test)
+ -- set font doesnt return anything so draw with the test font
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setFont(Font)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.print('love', 0, 3)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {
+ {0,0},{0,6},{2,6},{6,2},
+ {4,4},{8,4},{6,6},{10,2},
+ {14,2},{12,6}
+ }
+ }, 'set font for print')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setFrontFaceWinding
+love.test.graphics.setFrontFaceWinding = function(test)
+ -- check documented modes are valid
+ local original = love.graphics.getFrontFaceWinding()
+ love.graphics.setFrontFaceWinding('cw')
+ test:assertEquals('cw', love.graphics.getFrontFaceWinding(), 'check ffw cw set')
+ love.graphics.setFrontFaceWinding('ccw')
+ test:assertEquals('ccw', love.graphics.getFrontFaceWinding(), 'check ffw ccw set')
+ love.graphics.setFrontFaceWinding(original)
+ -- @TODO better graphics drawing specific test
+end
+
+
+-- love.graphics.setLineJoin
+love.test.graphics.setLineJoin = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setFont(Font)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ local line = {0,1,8,1,8,8}
+ love.graphics.setLineStyle('rough')
+ love.graphics.setLineWidth(2)
+ love.graphics.setColor(1, 0, 0)
+ love.graphics.setLineJoin('bevel')
+ love.graphics.line(line)
+ love.graphics.translate(0, 4)
+ love.graphics.setColor(1, 1, 0)
+ love.graphics.setLineJoin('none')
+ love.graphics.line(line)
+ love.graphics.translate(0, 4)
+ love.graphics.setColor(0, 0, 1)
+ love.graphics.setLineJoin('miter')
+ love.graphics.line(line)
+ love.graphics.setColor(1, 1, 1)
+ love.graphics.setLineWidth(1)
+ love.graphics.origin()
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ black = {{8,0}},
+ red = {{8,4}},
+ yellow = {{8,7}},
+ blue = {{8,8}}
+ }, 'set line join')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setLineStyle
+love.test.graphics.setLineStyle = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setFont(Font)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0)
+ local line = {0,1,16,1}
+ love.graphics.setLineStyle('rough')
+ love.graphics.line(line)
+ love.graphics.translate(0, 4)
+ love.graphics.setLineStyle('smooth')
+ love.graphics.line(line)
+ love.graphics.setLineStyle('rough')
+ love.graphics.setColor(1, 1, 1)
+ love.graphics.origin()
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{0,0},{7,0},{15,0}},
+ red07 = {{0,4},{7,4},{15,4}}
+ }, 'set line style')
+ -- linux runner needs a 1/255 tolerance for the blend between a rough line + bg
+ if GITHUB_RUNNER == true and love.system.getOS() == 'Linux' then
+ test.rgba_tolerance = 1
+ end
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setLineWidth
+love.test.graphics.setLineWidth = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setFont(Font)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ local line = {0,1,8,1,8,8}
+ love.graphics.setColor(1, 0, 0)
+ love.graphics.setLineWidth(2)
+ love.graphics.line(line)
+ love.graphics.translate(0, 4)
+ love.graphics.setColor(1, 1, 0)
+ love.graphics.setLineWidth(3)
+ love.graphics.line(line)
+ love.graphics.translate(0, 4)
+ love.graphics.setColor(0, 0, 1)
+ love.graphics.setLineWidth(4)
+ love.graphics.line(line)
+ love.graphics.setColor(1, 1, 1)
+ love.graphics.setLineWidth(1)
+ love.graphics.origin()
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ black = {{0,2},{6,2},{0,6},{5,6},{0,11},{5,11}},
+ red = {{0,0},{0,1},{7,2},{8,2}},
+ yellow = {{0,3},{0,5},{6,6},{8,6}},
+ blue = {{0,7},{0,10},{6,15},{9,15}}
+ }, 'set line width')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setMeshCullMode
+love.test.graphics.setMeshCullMode = function(test)
+ -- check documented modes are valid
+ local original = love.graphics.getMeshCullMode()
+ local modes = {'back', 'front', 'none'}
+ for m=1,#modes do
+ love.graphics.setMeshCullMode(modes[m])
+ test:assertEquals(modes[m], love.graphics.getMeshCullMode(), 'check mesh cull mode ' .. modes[m] .. ' was set')
+ end
+ love.graphics.setMeshCullMode(original)
+ -- @TODO better graphics drawing specific test
+end
+
+
+-- love.graphics.setScissor
+love.test.graphics.setScissor = function(test)
+ -- make a scissor for the left half
+ -- then we should be able to fill the canvas with red and only left is filled
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.origin()
+ love.graphics.setScissor(0, 0, 8, 16)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 16, 16)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setScissor()
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{0,0},{7,0},{0,15},{7,15}},
+ black ={{8,0},{8,15},{15,0},{15,15}}
+ }, 'set scissor')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setShader
+love.test.graphics.setShader = function(test)
+ -- make a shader that will only ever draw yellow
+ local pixelcode = [[
+ vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords) {
+ vec4 texturecolor = Texel(tex, texture_coords);
+ return vec4(1.0,1.0,0.0,1.0);
+ }
+ ]]
+ local vertexcode = [[
+ vec4 position(mat4 transform_projection, vec4 vertex_position) {
+ return transform_projection * vertex_position;
+ }
+ ]]
+ local shader = love.graphics.newShader(pixelcode, vertexcode)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setShader(shader)
+ -- draw red rectangle
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 16, 16)
+ love.graphics.setShader()
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ yellow = {{0,0},{15,0},{0,15},{15,15}},
+ }, 'check shader set to yellow')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setStencilMode
+love.test.graphics.setStencilMode = function(test)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas({canvas, stencil=true})
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setStencilMode('replace', 'always', 1)
+ love.graphics.circle('fill', 8, 8, 6)
+ love.graphics.setStencilMode('keep', 'greater', 0)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 16, 16)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setStencilMode()
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, {
+ red = {{6,2},{9,2},{2,6},{2,9},{13,6},{9,6},{6,13},{9,13}}
+ }, 'check stencil test')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.setWireframe
+love.test.graphics.setWireframe = function(test)
+ local name, version, vendor, device = love.graphics.getRendererInfo()
+ if string.match(name, 'OpenGL ES') then
+ test:skipTest('Wireframe not supported on OpenGL ES')
+ else
+ -- check wireframe outlines
+ love.graphics.setWireframe(true)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 1, 0, 1)
+ love.graphics.rectangle('fill', 2, 2, 13, 13)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ love.graphics.setWireframe(false)
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ -- on macOS runners wireframes are drawn 1px off from the target
+ if GITHUB_RUNNER == true and love.system.getOS() == 'OS X' then
+ test.pixel_tolerance = 1
+ end
+ test:compareImg(imgdata)
+ end
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+-------------------------------COORDINATE SYSTEM--------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.graphics.applyTransform
+love.test.graphics.applyTransform = function(test)
+ -- use transform object to translate the drawn rectangle
+ local transform = love.math.newTransform()
+ transform:translate(10, 0)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.applyTransform(transform)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{10, 0}} }, 'apply transform 10')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.inverseTransformPoint
+love.test.graphics.inverseTransformPoint = function(test)
+ -- start with 0, 0
+ local sx, sy = love.graphics.inverseTransformPoint(0, 0)
+ test:assertEquals(0, sx, 'check starting x is 0')
+ test:assertEquals(0, sy, 'check starting y is 0')
+ -- check translation effects the point
+ love.graphics.translate(1, 5)
+ sx, sy = love.graphics.inverseTransformPoint(1, 5)
+ test:assertEquals(0, sx, 'check transformed x is 0')
+ test:assertEquals(0, sy, 'check transformed y is 0')
+ love.graphics.origin()
+end
+
+
+-- love.graphics.origin
+love.test.graphics.origin = function(test)
+ -- if we do some translations and scaling
+ -- using .origin() should reset it all and draw the pixel at 0,0
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.origin()
+ love.graphics.translate(10, 10)
+ love.graphics.scale(1, 1)
+ love.graphics.shear(20, 20)
+ love.graphics.origin()
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{0, 0}} }, 'origin check')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.pop
+love.test.graphics.pop = function(test)
+ -- if we push at the start, and then run a pop
+ -- it should reset it all and draw the pixel at 0,0
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.push()
+ love.graphics.translate(10, 10)
+ love.graphics.scale(1, 1)
+ love.graphics.shear(20, 20)
+ love.graphics.pop()
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{0, 0}} }, 'pop 1')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.push
+love.test.graphics.push = function(test)
+ -- if we push at the start, do some stuff, then another push
+ -- 1 pop should only go back 1 push and draw the pixel at 1, 1
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.push()
+ love.graphics.scale(1, 1)
+ love.graphics.shear(20, 20)
+ love.graphics.push()
+ love.graphics.translate(1, 1)
+ love.graphics.pop()
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{1, 1}} }, 'push 1')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.replaceTransform
+love.test.graphics.replaceTransform = function(test)
+ -- if use transform object to translate
+ -- set some normal transforms first which should get overwritten
+ local transform = love.math.newTransform()
+ transform:translate(10, 0)
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.scale(2, 2)
+ love.graphics.translate(10, 10)
+ love.graphics.replaceTransform(transform)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{10, 0}} }, 'replace transform 10')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.rotate
+love.test.graphics.rotate = function(test)
+ -- starting at 0,0, we rotate by 90deg and then draw
+ -- we can then check the drawn rectangle is rotated
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.translate(4, 0)
+ love.graphics.rotate(90 * (math.pi/180))
+ love.graphics.rectangle('fill', 0, 0, 4, 4)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{0,0},{3,0},{3,3},{0,3}} }, 'rotate 90')
+ test:compareImg(imgdata)
+end
+
+
+-- love.graphics.scale
+love.test.graphics.scale = function(test)
+ -- starting at 0,0, we scale by 4x and then draw
+ -- we can then check the drawn rectangle covers the whole canvas
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.scale(4, 4)
+ love.graphics.rectangle('fill', 0, 0, 4, 4)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{1,1},{1,15},{15,1},{15,15}} }, 'scale 4x')
+end
+
+
+-- love.graphics.shear
+love.test.graphics.shear = function(test)
+ -- starting at 0,0, we shear by 2x and then draw
+ -- we can then check the drawn rectangle has moved over
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.origin()
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.shear(2, 0)
+ love.graphics.rectangle('fill', 0, 0, 4, 4)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata1 = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata1, { red = {{1,0},{4,0},{7,3},{10,3}} }, 'shear x')
+ -- same again at 0,0, we shear by 2y and then draw
+ -- we can then check the drawn rectangle has moved down
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.origin()
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.shear(0, 2)
+ love.graphics.rectangle('fill', 0, 0, 4, 4)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata2 = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata2, { red = { {0,1},{0,4},{3,7},{3,10}} }, 'shear y')
+ test:compareImg(imgdata1)
+ test:compareImg(imgdata2)
+end
+
+
+-- love.graphics.transformPoint
+love.test.graphics.transformPoint = function(test)
+ -- start with 0, 0
+ local sx, sy = love.graphics.transformPoint(0, 0)
+ test:assertEquals(0, sx, 'check starting x is 0')
+ test:assertEquals(0, sy, 'check starting y is 0')
+ -- check translation effects the point
+ love.graphics.translate(1, 5)
+ sx, sy = love.graphics.transformPoint(0, 0)
+ test:assertEquals(1, sx, 'check transformed x is 0')
+ test:assertEquals(5, sy, 'check transformed y is 10')
+end
+
+
+-- love.graphics.translate
+love.test.graphics.translate = function(test)
+ -- starting at 0,0, we translate 4 times and draw a pixel at each point
+ -- we can then check the 4 points are now red
+ local canvas = love.graphics.newCanvas(16, 16)
+ love.graphics.setCanvas(canvas)
+ love.graphics.clear(0, 0, 0, 1)
+ love.graphics.setColor(1, 0, 0, 1)
+ love.graphics.translate(5, 0)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.translate(0, 5)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.translate(-5, 0)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.translate(0, -5)
+ love.graphics.rectangle('fill', 0, 0, 1, 1)
+ love.graphics.setColor(1, 1, 1, 1)
+ love.graphics.setCanvas()
+ local imgdata = love.graphics.readbackTexture(canvas, {16, 0, 0, 0, 16, 16})
+ test:assertPixels(imgdata, { red = {{5,0},{0,5},{5,5},{0,0}} }, 'translate 4x')
+ test:compareImg(imgdata)
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+-------------------------------------WINDOW-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.graphics.getDPIScale
+-- @NOTE hardware dependent so can't check result
+love.test.graphics.getDPIScale = function(test)
+ test:assertNotNil(love.graphics.getDPIScale())
+end
+
+
+-- love.graphics.getDimensions
+love.test.graphics.getDimensions = function(test)
+ -- check graphics dimensions match window dimensions
+ local gwidth, gheight = love.graphics.getDimensions()
+ local wwidth, wheight, _ = love.window.getMode()
+ test:assertEquals(wwidth, gwidth, 'check graphics dimension w matches window w')
+ test:assertEquals(wheight, gheight, 'check graphics dimension h matches window h')
+end
+
+
+-- love.graphics.getHeight
+love.test.graphics.getHeight = function(test)
+ -- check graphics height match window height
+ local wwidth, wheight, _ = love.window.getMode()
+ test:assertEquals(wheight, love.graphics.getHeight(), 'check graphics h matches window h')
+end
+
+
+-- love.graphics.getPixelDimensions
+love.test.graphics.getPixelDimensions = function(test)
+ -- check graphics dimensions match window dimensions relative to dpi
+ local dpi = love.graphics.getDPIScale()
+ local gwidth, gheight = love.graphics.getPixelDimensions()
+ local wwidth, wheight, _ = love.window.getMode()
+ test:assertEquals(wwidth, gwidth/dpi, 'check graphics pixel dpi w matches window w')
+ test:assertEquals(wheight, gheight/dpi, 'check graphics pixel dpi h matches window h')
+end
+
+
+-- love.graphics.getPixelHeight
+love.test.graphics.getPixelHeight = function(test)
+ -- check graphics height match window height relative to dpi
+ local dpi = love.graphics.getDPIScale()
+ local wwidth, wheight, _ = love.window.getMode()
+ test:assertEquals(wheight,love.graphics.getPixelHeight()/dpi, 'check graphics pixel dpi h matches window h')
+end
+
+
+-- love.graphics.getPixelWidth
+love.test.graphics.getPixelWidth = function(test)
+ -- check graphics width match window width relative to dpi
+ local dpi = love.graphics.getDPIScale()
+ local wwidth, wheight, _ = love.window.getMode()
+ test:assertEquals(wwidth, love.graphics.getWidth()/dpi, 'check graphics pixel dpi w matches window w')
+end
+
+
+-- love.graphics.getWidth
+love.test.graphics.getWidth = function(test)
+ -- check graphics width match window width
+ local wwidth, wheight, _ = love.window.getMode()
+ test:assertEquals(wwidth, love.graphics.getWidth(), 'check graphics w matches window w')
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+-------------------------------SYSTEM INFORMATION-------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.graphics.getTextureFormats
+love.test.graphics.getTextureFormats = function(test)
+ local formats = {
+ 'hdr', 'r8i', 'r8ui', 'r16i', 'r16ui', 'r32i', 'r32ui', 'rg8i', 'rg8ui',
+ 'rg16i', 'rg16ui', 'rg32i', 'rg32ui', 'bgra8', 'r8', 'rgba8i', 'rgba8ui',
+ 'rgba16i', 'rg8', 'rgba32i', 'rgba32ui', 'rgba8', 'DXT1', 'r16', 'DXT5',
+ 'rg16', 'BC4s', 'rgba16', 'BC5s', 'r16f', 'BC6hs', 'BC7', 'PVR1rgb2',
+ 'rg16f', 'PVR1rgba2', 'rgba16f', 'ETC1', 'r32f', 'ETC2rgba', 'rg32f',
+ 'EACr', 'rgba32f', 'EACrg', 'rgba4', 'ASTC4x4', 'ASTC5x4', 'rgb5a1',
+ 'ASTC6x5', 'rgb565', 'ASTC8x5', 'ASTC8x6', 'rgb10a2', 'ASTC10x5',
+ 'rg11b10f', 'ASTC10x8', 'ASTC10x10', 'ASTC12x10', 'ASTC12x12', 'normal',
+ 'srgba8', 'la8', 'ASTC10x6', 'ASTC8x8', 'ASTC6x6', 'ASTC5x5', 'EACrgs',
+ 'EACrs', 'ETC2rgba1', 'ETC2rgb', 'PVR1rgba4', 'PVR1rgb4', 'BC6h',
+ 'BC5', 'BC4', 'DXT3', 'stencil8', 'rgba16ui', 'bgra8srgb'
+ }
+ local supported = love.graphics.getTextureFormats({ canvas = true })
+ test:assertNotNil(supported)
+ for f=1,#formats do
+ test:assertNotEquals(nil, supported[formats[f] ], 'expected a key for format: ' .. formats[f])
+ end
+end
+
+
+-- love.graphics.getRendererInfo
+-- @NOTE hardware dependent so best can do is nil checking
+love.test.graphics.getRendererInfo = function(test)
+ local name, version, vendor, device = love.graphics.getRendererInfo()
+ test:assertNotNil(name)
+ test:assertNotNil(version)
+ test:assertNotNil(vendor)
+ test:assertNotNil(device)
+end
+
+
+-- love.graphics.getStats
+-- @NOTE cant really predict some of these so just nil check for most
+love.test.graphics.getStats = function(test)
+ local stattypes = {
+ 'drawcalls', 'canvasswitches', 'texturememory', 'shaderswitches',
+ 'drawcallsbatched', 'textures', 'fonts'
+ }
+ local stats = love.graphics.getStats()
+ for s=1,#stattypes do
+ test:assertNotEquals(nil, stats[stattypes[s] ], 'expected a key for stat: ' .. stattypes[s])
+ end
+end
+
+
+-- love.graphics.getSupported
+love.test.graphics.getSupported = function(test)
+ -- cant check values as hardware dependent but we can check the keys in the
+ -- table match what the documentation lists
+ local gfs = {
+ 'clampzero', 'lighten', 'glsl3', 'instancing', 'fullnpot',
+ 'pixelshaderhighp', 'shaderderivatives', 'indirectdraw', 'mipmaprange',
+ 'copyrendertargettobuffer', 'copytexturetobuffer', 'copybuffer',
+ 'indexbuffer32bit', 'multirendertargetformats', 'clampone', 'blendminmax',
+ 'glsl4'
+ }
+ local features = love.graphics.getSupported()
+ for g=1,#gfs do
+ test:assertNotEquals(nil, features[gfs[g] ], 'expected a key for graphic feature: ' .. gfs[g])
+ end
+end
+
+
+-- love.graphics.getSystemLimits
+love.test.graphics.getSystemLimits = function(test)
+ -- cant check values as hardware dependent but we can check the keys in the
+ -- table match what the documentation lists
+ local glimits = {
+ 'texelbuffersize', 'shaderstoragebuffersize', 'threadgroupsx',
+ 'threadgroupsy', 'pointsize', 'texturesize', 'texturelayers', 'volumetexturesize',
+ 'cubetexturesize', 'anisotropy', 'texturemsaa', 'rendertargets', 'threadgroupsz'
+ }
+ local limits = love.graphics.getSystemLimits()
+ for g=1,#glimits do
+ test:assertNotEquals(nil, limits[glimits[g] ], 'expected a key for system limit: ' .. glimits[g])
+ end
+end
+
+
+-- love.graphics.getTextureTypes
+love.test.graphics.getTextureTypes = function(test)
+ -- cant check values as hardware dependent but we can check the keys in the
+ -- table match what the documentation lists
+ local ttypes = {
+ '2d', 'array', 'cube', 'volume'
+ }
+ local types = love.graphics.getTextureTypes()
+ for t=1,#ttypes do
+ test:assertNotEquals(nil, types[ttypes[t] ], 'expected a key for texture type: ' .. ttypes[t])
+ end
+end
diff --git a/testing/tests/image.lua b/testing/tests/image.lua
new file mode 100644
index 000000000..fa9f4cbd2
--- /dev/null
+++ b/testing/tests/image.lua
@@ -0,0 +1,119 @@
+-- love.image
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- CompressedImageData (love.image.newCompressedImageData)
+love.test.image.CompressedImageData = function(test)
+
+ -- create obj
+ local idata = love.image.newCompressedData('resources/love.dxt1')
+ test:assertObject(idata)
+
+ -- check string + size
+ test:assertNotEquals(nil, idata:getString(), 'check data string')
+ test:assertEquals(2744, idata:getSize(), 'check data size')
+
+ -- check img dimensions
+ local iw, ih = idata:getDimensions()
+ test:assertEquals(64, iw, 'check image dimension w')
+ test:assertEquals(64, ih, 'check image dimension h')
+ test:assertEquals(64, idata:getWidth(), 'check image direct w')
+ test:assertEquals(64, idata:getHeight(), 'check image direct h')
+
+ -- check format
+ test:assertEquals('DXT1', idata:getFormat(), 'check image format')
+
+ -- check mipmap count
+ test:assertEquals(7, idata:getMipmapCount(), 'check mipmap count')
+
+end
+
+
+-- ImageData (love.image.newImageData)
+love.test.image.ImageData = function(test)
+
+ -- create obj
+ local idata = love.image.newImageData('resources/love.png')
+ test:assertObject(idata)
+
+ -- check string + size
+ test:assertNotEquals(nil, idata:getString(), 'check data string')
+ test:assertEquals(16384, idata:getSize(), 'check data size')
+
+ -- check img dimensions
+ local iw, ih = idata:getDimensions()
+ test:assertEquals(64, iw, 'check image dimension w')
+ test:assertEquals(64, ih, 'check image dimension h')
+ test:assertEquals(64, idata:getWidth(), 'check image direct w')
+ test:assertEquals(64, idata:getHeight(), 'check image direct h')
+
+ -- check format
+ test:assertEquals('rgba8', idata:getFormat(), 'check image format')
+
+ -- manipulate image data so white heart is black
+ local mapdata = function(x, y, r, g, b, a)
+ if r == 1 and g == 1 and b == 1 then
+ r = 0; g = 0; b = 0
+ end
+ return r, g, b, a
+ end
+ idata:mapPixel(mapdata, 0, 0, 64, 64)
+ local r1, g1, b1 = idata:getPixel(25, 25)
+ test:assertEquals(0, r1+g1+b1, 'check mapped black')
+
+ -- map some other data into the idata
+ local idata2 = love.image.newImageData('resources/loveinv.png')
+ idata:paste(idata2, 0, 0, 0, 0)
+ r1, g1, b1 = idata:getPixel(25, 25)
+ test:assertEquals(3, r1+g1+b1, 'check back to white')
+
+ -- set pixels directly
+ idata:setPixel(25, 25, 1, 0, 0, 1)
+ local r2, g2, b2 = idata:getPixel(25, 25)
+ test:assertEquals(1, r2+g2+b2, 'check set to red')
+
+ -- check encoding to an image
+ idata:encode('png', 'test-encode.png')
+ local read = love.filesystem.openFile('test-encode.png', 'r')
+ test:assertNotNil(read)
+ love.filesystem.remove('test-encode.png')
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.image.isCompressed
+-- @NOTE really we need to test each of the files listed here:
+-- https://love2d.org/wiki/CompressedImageFormat
+-- also need to be platform dependent (e.g. dxt not suppored on phones)
+love.test.image.isCompressed = function(test)
+ test:assertTrue(love.image.isCompressed('resources/love.dxt1'),
+ 'check dxt1 valid compressed image')
+end
+
+
+-- love.image.newCompressedData
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.image.newCompressedData = function(test)
+ test:assertObject(love.image.newCompressedData('resources/love.dxt1'))
+end
+
+
+-- love.image.newImageData
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.image.newImageData = function(test)
+ test:assertObject(love.image.newImageData('resources/love.png'))
+ test:assertObject(love.image.newImageData(16, 16, 'rgba8', nil))
+end
diff --git a/testing/tests/math.lua b/testing/tests/math.lua
new file mode 100644
index 000000000..7ae40027d
--- /dev/null
+++ b/testing/tests/math.lua
@@ -0,0 +1,375 @@
+-- love.math
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- BezierCurve (love.math.newBezierCurve)
+love.test.math.BezierCurve = function(test)
+
+ -- create obj
+ local curve = love.math.newBezierCurve(1, 1, 2, 2, 3, 1)
+ local px, py = curve:getControlPoint(2)
+ test:assertObject(curve)
+
+ -- check initial properties
+ test:assertCoords({2, 2}, {px, py}, 'check point x/y')
+ test:assertEquals(3, curve:getControlPointCount(), 'check 3 points')
+ test:assertEquals(2, curve:getDegree(), 'check degree is points-1')
+
+ -- check some values on the curve
+ test:assertEquals(1, curve:evaluate(0), 'check curve evaluation 0')
+ test:assertRange(curve:evaluate(0.1), 1.2, 1.3, 'check curve evaluation 0.1')
+ test:assertRange(curve:evaluate(0.2), 1.4, 1.5, 'check curve evaluation 0.2')
+ test:assertRange(curve:evaluate(0.5), 2, 2.1, 'check curve evaluation 0.5')
+ test:assertEquals(3, curve:evaluate(1), 'check curve evaluation 1')
+
+ -- check derivative
+ local deriv = curve:getDerivative()
+ test:assertObject(deriv)
+ test:assertEquals(2, deriv:getControlPointCount(), 'check deriv points')
+ test:assertRange(deriv:evaluate(0.1), 2, 2.1, 'check deriv evaluation 0.1')
+
+ -- check segment
+ local segment = curve:getSegment(0, 0.5)
+ test:assertObject(segment)
+ test:assertEquals(3, segment:getControlPointCount(), 'check segment points')
+ test:assertRange(segment:evaluate(0.1), 1, 1.1, 'check segment evaluation 0.1')
+
+ -- mess with control points
+ curve:removeControlPoint(2)
+ curve:insertControlPoint(4, 1, -1)
+ curve:insertControlPoint(5, 3, -1)
+ curve:insertControlPoint(6, 2, -1)
+ curve:setControlPoint(2, 3, 2)
+ test:assertEquals(5, curve:getControlPointCount(), 'check 3 points still')
+ local px1, py1 = curve:getControlPoint(1)
+ local px2, py2 = curve:getControlPoint(3)
+ local px3, py3 = curve:getControlPoint(5)
+ test:assertCoords({1, 1}, {px1, py1}, 'check modified point 1')
+ test:assertCoords({5, 3}, {px2, py2}, 'check modified point 1')
+ test:assertCoords({3, 1}, {px3, py3}, 'check modified point 1')
+
+ -- check render lists
+ local coords1 = curve:render(5)
+ local coords2 = curve:renderSegment(0, 0.1, 5)
+ test:assertEquals(196, #coords1, 'check coords')
+ test:assertEquals(20, #coords2, 'check segment coords')
+
+ -- check translation values
+ px, py = curve:getControlPoint(2)
+ test:assertCoords({3, 2}, {px, py}, 'check pretransform x/y')
+ curve:rotate(90 * (math.pi/180), 0, 0)
+ px, py = curve:getControlPoint(2)
+ test:assertCoords({-2, 3}, {px, py}, 'check rotated x/y')
+ curve:scale(2, 0, 0)
+ px, py = curve:getControlPoint(2)
+ test:assertCoords({-4, 6}, {px, py}, 'check scaled x/y')
+ curve:translate(5, -5)
+ px, py = curve:getControlPoint(2)
+ test:assertCoords({1, 1}, {px, py}, 'check translated x/y')
+
+end
+
+
+-- RandomGenerator (love.math.RandomGenerator)
+-- @NOTE as this checks random numbers the chances this fails is very unlikely, but not 0...
+-- if you've managed to proc it congrats! your prize is to rerun the testsuite again
+love.test.math.RandomGenerator = function(test)
+
+ -- create object
+ local rng1 = love.math.newRandomGenerator(3418323524, 20529293)
+ test:assertObject(rng1)
+
+ -- check set properties
+ local low, high = rng1:getSeed()
+ test:assertEquals(3418323524, low, 'check seed low')
+ test:assertEquals(20529293, high, 'check seed high')
+
+ -- check states
+ local rng2 = love.math.newRandomGenerator(1448323524, 10329293)
+ test:assertNotEquals(rng1:random(), rng2:random(), 'check not matching states')
+ test:assertNotEquals(rng1:randomNormal(), rng2:randomNormal(), 'check not matching states')
+
+ -- check setting state works
+ rng2:setState(rng1:getState())
+ test:assertEquals(rng1:random(), rng2:random(), 'check now matching')
+
+ -- check overwriting seed works, should change output
+ rng1:setSeed(os.time())
+ test:assertNotEquals(rng1:random(), rng2:random(), 'check not matching states')
+ test:assertNotEquals(rng1:randomNormal(), rng2:randomNormal(), 'check not matching states')
+
+end
+
+
+-- Transform (love.math.Transform)
+love.test.math.Transform = function(test)
+
+ -- create obj
+ local transform = love.math.newTransform(0, 0, 0, 1, 1, 0, 0, 0, 0)
+ test:assertObject(transform)
+
+ -- set some values and check the matrix and transformPoint values
+ transform:translate(10, 8)
+ transform:scale(2, 3)
+ transform:rotate(90*(math.pi/180))
+ transform:shear(1, 2)
+ local px, py = transform:transformPoint(1, 1)
+ test:assertCoords({4, 14}, {px, py}, 'check transformation methods')
+ transform:reset()
+ px, py = transform:transformPoint(1, 1)
+ test:assertCoords({1, 1}, {px, py}, 'check reset')
+
+ -- apply a transform to another transform
+ local transform2 = love.math.newTransform()
+ transform2:translate(5, 3)
+ transform:apply(transform2)
+ px, py = transform:transformPoint(1, 1)
+ test:assertCoords({6, 4}, {px, py}, 'check apply other transform')
+
+ -- check cloning a transform
+ local transform3 = transform:clone()
+ px, py = transform3:transformPoint(1, 1)
+ test:assertCoords({6, 4}, {px, py}, 'check clone transform')
+
+ -- check inverse and inverseTransform
+ transform:reset()
+ transform:translate(-14, 6)
+ local ipx, ipy = transform:inverseTransformPoint(0, 0)
+ transform:inverse()
+ px, py = transform:transformPoint(0, 0)
+ test:assertCoords({-px, -py}, {ipx, ipy}, 'check inverse points transform')
+
+ -- check matrix manipulation
+ transform:setTransformation(0, 0, 0, 1, 1, 0, 0, 0, 0)
+ transform:translate(4, 4)
+ local m1, m2, m3, m4, m5, m6, m7, m8,
+ m9, m10, m11, m12, m13, m14, m15, m16 = transform:getMatrix()
+ test:assertEquals(4, m4, 'check translate matrix x')
+ test:assertEquals(4, m8, 'check translate matrix y')
+ transform:setMatrix(m1, m2, m3, 3, m5, m6, m7, 1, m9, m10, m11, m12, m13, m14, m15, m16)
+ px, py = transform:transformPoint(1, 1)
+ test:assertCoords({4, 2}, {px, py}, 'check set matrix')
+
+ -- check affine vs non affine
+ transform:reset()
+ test:assertTrue(transform:isAffine2DTransform(), 'check affine 1')
+ transform:translate(4, 3)
+ test:assertTrue(transform:isAffine2DTransform(), 'check affine 2')
+ transform:setMatrix(1, 3, 4, 5.5, 1, 4.5, 2, 1, 3.4, 5.1, 4.1, 13, 1, 1, 2, 3)
+ test:assertFalse(transform:isAffine2DTransform(), 'check not affine')
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.math.colorFromBytes
+love.test.math.colorFromBytes = function(test)
+ -- check random value
+ local r1, g1, b1, a1 = love.math.colorFromBytes(51, 51, 51, 51)
+ test:assertEquals(r1, 0.2, 'check r from bytes')
+ test:assertEquals(g1, 0.2, 'check g from bytes')
+ test:assertEquals(b1, 0.2, 'check b from bytes')
+ test:assertEquals(a1, 0.2, 'check a from bytes')
+ -- check "max" value
+ local r2, g2, b2, a2 = love.math.colorFromBytes(255, 255, 255, 255)
+ test:assertEquals(r2, 1, 'check r from bytes')
+ test:assertEquals(g2, 1, 'check g from bytes')
+ test:assertEquals(b2, 1, 'check b from bytes')
+ test:assertEquals(a2, 1, 'check a from bytes')
+ -- check "min" value
+ local r3, g3, b3, a3 = love.math.colorFromBytes(0, 0, 0, 0)
+ test:assertEquals(r3, 0, 'check r from bytes')
+ test:assertEquals(g3, 0, 'check g from bytes')
+ test:assertEquals(b3, 0, 'check b from bytes')
+ test:assertEquals(a3, 0, 'check a from bytes')
+end
+
+
+-- love.math.colorToBytes
+love.test.math.colorToBytes = function(test)
+ -- check random value
+ local r1, g1, b1, a1 = love.math.colorToBytes(0.2, 0.2, 0.2, 0.2)
+ test:assertEquals(r1, 51, 'check bytes from r')
+ test:assertEquals(g1, 51, 'check bytes from g')
+ test:assertEquals(b1, 51, 'check bytes from b')
+ test:assertEquals(a1, 51, 'check bytes from a')
+ -- check "max" value
+ local r2, g2, b2, a2 = love.math.colorToBytes(1, 1, 1, 1)
+ test:assertEquals(r2, 255, 'check bytes from r')
+ test:assertEquals(g2, 255, 'check bytes from g')
+ test:assertEquals(b2, 255, 'check bytes from b')
+ test:assertEquals(a2, 255, 'check bytes from a')
+ -- check "min" value
+ local r3, g3, b3, a3 = love.math.colorToBytes(0, 0, 0, 0)
+ test:assertEquals(r3, 0, 'check bytes from r')
+ test:assertEquals(g3, 0, 'check bytes from g')
+ test:assertEquals(b3, 0, 'check bytes from b')
+ test:assertEquals(a3, 0, 'check bytes from a')
+end
+
+
+-- love.math.gammaToLinear
+-- @NOTE I tried doing the same formula as the source from MathModule.cpp
+-- but get test failues due to slight differences
+love.test.math.gammaToLinear = function(test)
+ local lr, lg, lb = love.math.gammaToLinear(1, 0.8, 0.02)
+ --local eg = ((0.8 + 0.055) / 1.055)^2.4
+ --local eb = 0.02 / 12.92
+ test:assertGreaterEqual(0, lr, 'check gamma r to linear')
+ test:assertGreaterEqual(0, lg, 'check gamma g to linear')
+ test:assertGreaterEqual(0, lb, 'check gamma b to linear')
+end
+
+
+-- love.math.getRandomSeed
+-- @NOTE whenever i run this high is always 0, is that intended?
+love.test.math.getRandomSeed = function(test)
+ local low, high = love.math.getRandomSeed()
+ test:assertGreaterEqual(0, low, 'check random seed low')
+ test:assertGreaterEqual(0, high, 'check random seed high')
+end
+
+
+-- love.math.getRandomState
+love.test.math.getRandomState = function(test)
+ test:assertNotNil(love.math.getRandomState())
+end
+
+
+-- love.math.isConvex
+love.test.math.isConvex = function(test)
+ local isconvex = love.math.isConvex({0, 0, 1, 0, 1, 1, 1, 0, 0, 0}) -- square
+ local notconvex = love.math.isConvex({1, 2, 2, 4, 3, 4, 2, 1, 3, 1}) -- weird shape
+ test:assertTrue(isconvex, 'check polygon convex')
+ test:assertFalse(notconvex, 'check polygon not convex')
+end
+
+
+-- love.math.linearToGammer
+-- @NOTE I tried doing the same formula as the source from MathModule.cpp
+-- but get test failues due to slight differences
+love.test.math.linearToGamma = function(test)
+ local gr, gg, gb = love.math.linearToGamma(1, 0.8, 0.001)
+ --local eg = 1.055 * (0.8^1/2.4) - 0.055
+ --local eb = 0.001 * 12.92
+ test:assertGreaterEqual(0, gr, 'check linear r to gamme')
+ test:assertGreaterEqual(0, gg, 'check linear g to gamme')
+ test:assertGreaterEqual(0, gb, 'check linear b to gamme')
+end
+
+
+-- love.math.newBezierCurve
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.math.newBezierCurve = function(test)
+ test:assertObject(love.math.newBezierCurve({0, 0, 0, 1, 1, 1, 2, 1}))
+end
+
+
+-- love.math.newRandomGenerator
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.math.newRandomGenerator = function(test)
+ test:assertObject(love.math.newRandomGenerator())
+end
+
+
+-- love.math.newTransform
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.math.newTransform = function(test)
+ test:assertObject(love.math.newTransform())
+end
+
+
+-- love.math.perlinNoise
+love.test.math.perlinNoise = function(test)
+ -- check some noise values
+ -- output should be consistent if given the same input
+ local noise1 = love.math.perlinNoise(100)
+ local noise2 = love.math.perlinNoise(1, 10)
+ local noise3 = love.math.perlinNoise(1043, 31.123, 999)
+ local noise4 = love.math.perlinNoise(99.222, 10067, 8, 1843)
+ test:assertRange(noise1, 0.5, 0.51, 'check noise 1 dimension')
+ test:assertRange(noise2, 0.5, 0.51, 'check noise 2 dimensions')
+ test:assertRange(noise3, 0.56, 0.57, 'check noise 3 dimensions')
+ test:assertRange(noise4, 0.52, 0.53, 'check noise 4 dimensions')
+end
+
+
+-- love.math.simplexNoise
+love.test.math.simplexNoise = function(test)
+ -- check some noise values
+ -- output should be consistent if given the same input
+ local noise1 = love.math.simplexNoise(100)
+ local noise2 = love.math.simplexNoise(1, 10)
+ local noise3 = love.math.simplexNoise(1043, 31.123, 999)
+ local noise4 = love.math.simplexNoise(99.222, 10067, 8, 1843)
+ -- rounded to avoid floating point issues
+ test:assertRange(noise1, 0.5, 0.51, 'check noise 1 dimension')
+ test:assertRange(noise2, 0.47, 0.48, 'check noise 2 dimensions')
+ test:assertRange(noise3, 0.26, 0.27, 'check noise 3 dimensions')
+ test:assertRange(noise4, 0.53, 0.54, 'check noise 4 dimensions')
+end
+
+
+-- love.math.random
+love.test.math.random = function(test)
+ -- check some random ranges
+ test:assertRange(love.math.random(10), 1, 10, 'check within 1 - 10')
+ test:assertRange(love.math.random(10), 1, 10, 'check within 1 - 10')
+ test:assertRange(love.math.random(10), 1, 10, 'check within 1 - 10')
+ test:assertRange(love.math.random(10), 1, 10, 'check within 1 - 10')
+ test:assertRange(love.math.random(10), 1, 10, 'check within 1 - 10')
+ test:assertRange(love.math.random(5, 100), 5, 100, 'check within 5 - 100')
+ test:assertRange(love.math.random(5, 100), 5, 100, 'check within 5 - 100')
+ test:assertRange(love.math.random(5, 100), 5, 100, 'check within 5 - 100')
+ test:assertRange(love.math.random(5, 100), 5, 100, 'check within 5 - 100')
+ test:assertRange(love.math.random(5, 100), 5, 100, 'check within 5 - 100')
+end
+
+
+-- love.math.randomNormal
+-- @NOTE i dont _really_ get the range expected based on stddev + mean
+-- so feel free to change to be more accurate
+love.test.math.randomNormal = function(test)
+ test:assertRange(love.math.randomNormal(1, 0), -3, 3, 'check within -3 - 3')
+end
+
+
+-- love.math.setRandomSeed
+-- @NOTE same with getRandomSeed, high is always 0 when I tested it?
+love.test.math.setRandomSeed = function(test)
+ love.math.setRandomSeed(9001)
+ local low, high = love.math.getRandomSeed()
+ test:assertEquals(9001, low, 'check seed low set')
+ test:assertEquals(0, high, 'check seed high set')
+end
+
+
+-- love.math.setRandomState
+love.test.math.setRandomState = function(test)
+ -- check setting state matches value returned
+ local rs1 = love.math.getRandomState()
+ love.math.setRandomState(rs1)
+ local rs2 = love.math.getRandomState()
+ test:assertEquals(rs1, rs2, 'check random state set')
+end
+
+
+-- love.math.triangulate
+love.test.math.triangulate = function(test)
+ local triangles1 = love.math.triangulate({0, 0, 1, 0, 1, 1, 1, 0, 0, 0}) -- square
+ local triangles2 = love.math.triangulate({1, 2, 2, 4, 3, 4, 2, 1, 3, 1}) -- weird shape
+ test:assertEquals(3, #triangles1, 'check polygon triangles')
+ test:assertEquals(3, #triangles2, 'check polygon triangles')
+end
diff --git a/testing/tests/physics.lua b/testing/tests/physics.lua
new file mode 100644
index 000000000..2d426dbb1
--- /dev/null
+++ b/testing/tests/physics.lua
@@ -0,0 +1,824 @@
+-- love.physics
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------OBJECTS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- Body (love.physics.newBody)
+love.test.physics.Body = function(test)
+
+ -- create body
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 0, 0, 'static')
+ local body2 = love.physics.newBody(world, 30, 30, 'dynamic')
+ love.physics.newRectangleShape(body1, 5, 5, 10, 10)
+ love.physics.newRectangleShape(body2, 5, 5, 10, 10)
+ test:assertObject(body1)
+
+ -- check body active
+ test:assertTrue(body1:isActive(), 'check active by def')
+
+ -- check body bullet
+ test:assertFalse(body1:isBullet(), 'check not bullet by def')
+ body1:setBullet(true)
+ test:assertTrue(body1:isBullet(), 'check set bullet')
+
+ -- check fixed rotation
+ test:assertFalse(body1:isFixedRotation(), 'check fix rot def')
+ body1:setFixedRotation(true)
+ test:assertTrue(body1:isFixedRotation(), 'check set fix rot')
+
+ -- check sleeping/waking
+ test:assertTrue(body1:isSleepingAllowed(), 'check sleep def')
+ body1:setSleepingAllowed(false)
+ test:assertFalse(body1:isSleepingAllowed(), 'check set sleep')
+ body1:setSleepingAllowed(true)
+ world:update(1)
+ test:assertFalse(body1:isAwake(), 'check fell asleep')
+ body1:setSleepingAllowed(false)
+ body1:setType('dynamic')
+ test:assertTrue(body1:isAwake(), 'check waking up')
+
+ -- check touching
+ test:assertFalse(body1:isTouching(body2))
+ body2:setPosition(5, 5)
+ world:update(1)
+ test:assertTrue(body1:isTouching(body2))
+
+ -- check children lists
+ test:assertEquals(1, #body1:getContacts(), 'check contact list')
+ test:assertEquals(0, #body1:getJoints(), 'check joints list')
+ love.physics.newDistanceJoint(body1, body2, 5, 5, 10, 10, true)
+ test:assertEquals(1, #body1:getJoints(), 'check joints list')
+
+ -- check local points
+ local x, y = body1:getLocalCenter()
+ test:assertRange(x, 5, 6, 'check local center x')
+ test:assertRange(y, 5, 6, 'check local center y')
+ local lx, ly = body1:getLocalPoint(10, 10)
+ test:assertRange(lx, 10, 11, 'check local point x')
+ test:assertRange(ly, 9, 10, 'check local point y')
+ local lx1, ly1, lx2, ly2 = body1:getLocalPoints(0, 5, 5, 10)
+ test:assertRange(lx1, 0, 1, 'check local points x 1')
+ test:assertRange(ly1, 3, 4, 'check local points y 1')
+ test:assertRange(lx2, 5, 6, 'check local points x 2')
+ test:assertRange(ly2, 9, 10, 'check local points y 2')
+
+ -- check world points
+ local wx, wy = body1:getWorldPoint(10.4, 9)
+ test:assertRange(wx, 10, 11, 'check world point x')
+ test:assertRange(wy, 10, 11, 'check world point y')
+ local wx1, wy1, wx2, wy2 = body1:getWorldPoints(0.4, 4, 5.4, 9)
+ test:assertRange(wx1, 0, 1, 'check world points x 1')
+ test:assertRange(wy1, 5, 6, 'check world points y 1')
+ test:assertRange(wx2, 5, 6, 'check world points x 2')
+ test:assertRange(wy2, 10, 11, 'check world points y 2')
+
+ -- check angular damping + velocity
+ test:assertEquals(0, body1:getAngularDamping(), 'check angular damping')
+ test:assertEquals(0, body1:getAngularVelocity(), 'check angular velocity')
+
+ -- check world props
+ test:assertObject(body1:getWorld())
+ test:assertEquals(2, body1:getWorld():getBodyCount(), 'check world count')
+ local cx, cy = body1:getWorldCenter()
+ test:assertRange(cx, 4, 5, 'check world center x')
+ test:assertRange(cy, 6, 7, 'check world center y')
+ local vx, vy = body1:getWorldVector(5, 10)
+ test:assertEquals(5, vx, 'check vector x')
+ test:assertEquals(10, vy, 'check vector y')
+
+ -- check inertia
+ test:assertRange(body1:getInertia(), 5, 6, 'check inertia')
+
+ -- check angle
+ test:assertEquals(0, body1:getAngle(), 'check def angle')
+ body1:setAngle(90 * (math.pi/180))
+ test:assertEquals(math.floor(math.pi/2*100), math.floor(body1:getAngle()*100), 'check set angle')
+
+ -- check gravity scale
+ test:assertEquals(1, body1:getGravityScale(), 'check def grav')
+ body1:setGravityScale(2)
+ test:assertEquals(2, body1:getGravityScale(), 'check change grav')
+
+ -- check damping
+ test:assertEquals(0, body1:getLinearDamping(), 'check def lin damping')
+ body1:setLinearDamping(0.1)
+ test:assertRange(body1:getLinearDamping(), 0, 0.2, 'check change lin damping')
+
+ -- check velocity
+ local x2, y2 = body1:getLinearVelocity()
+ test:assertEquals(1, x2, 'check def lin velocity x')
+ test:assertEquals(1, y2, 'check def lin velocity y')
+ body1:setLinearVelocity(4, 5)
+ local x3, y3 = body1:getLinearVelocity()
+ test:assertEquals(4, x3, 'check change lin velocity x')
+ test:assertEquals(5, y3, 'check change lin velocity y')
+
+ -- check mass
+ test:assertRange(body1:getMass(), 0.1, 0.2, 'check def mass')
+ body1:setMass(10)
+ test:assertEquals(10, body1:getMass(), 'check change mass')
+ body1:setMassData(3, 5, 10, 1)
+ local x4, y4, mass4, inertia4 = body1:getMassData()
+ test:assertEquals(3, x4, 'check mass data change x')
+ test:assertEquals(5, y4, 'check mass data change y')
+ test:assertEquals(10, mass4, 'check mass data change mass')
+ test:assertRange(inertia4, 340, 341, 'check mass data change inertia')
+ body1:resetMassData()
+ local x5, y5, mass5, inertia5 = body1:getMassData()
+ test:assertRange(x5, 5, 6, 'check mass data reset x')
+ test:assertRange(y5, 5, 6, 'check mass data reset y')
+ test:assertRange(mass5, 0.1, 0.2, 'check mass data reset mass')
+ test:assertRange(inertia5, 5, 6, 'check mass data reset inertia')
+
+ -- check position
+ local x6, y6 = body1:getPosition()
+ test:assertRange(x6, -1, 0, 'check position x')
+ test:assertRange(y6, 0, 1, 'check position y')
+ body1:setPosition(10, 4)
+ local x7, y7 = body1:getPosition()
+ test:assertEquals(x7, 10, 'check set position x')
+ test:assertEquals(y7, 4, 'check set position y')
+
+ -- check type
+ test:assertEquals('dynamic', body1:getType(), 'check type match')
+ body1:setType('kinematic')
+ body1:setType('static')
+ test:assertEquals('static', body1:getType(), 'check type change')
+
+ -- check userdata
+ test:assertEquals(nil, body1:getUserData(), 'check user data')
+ body1:setUserData({ love = 'cool' })
+ test:assertEquals('cool', body1:getUserData().love, 'check set user data')
+
+ -- check x/y direct
+ test:assertEquals(10, math.floor(body1:getX()), 'check get x')
+ test:assertEquals(4, math.floor(body1:getY()), 'check get y')
+ body1:setX(0)
+ body1:setY(0)
+ test:assertEquals(0, body1:getX(), 'check get x')
+ test:assertEquals(0, body1:getY(), 'check get y')
+
+ -- apply angular impulse
+ local vel = body2:getAngularVelocity()
+ test:assertRange(vel, 0, 0, 'check velocity before')
+ body2:applyAngularImpulse(10)
+ local vel1 = body2:getAngularVelocity()
+ test:assertRange(vel1, 5, 6, 'check velocity after 1')
+
+ -- apply standard force
+ local ang1 = body2:getAngle()
+ test:assertRange(ang1, 0.1, 0.2, 'check initial angle 1')
+ body2:applyForce(2, 3)
+ world:update(2)
+ local vel2 = body2:getAngularVelocity()
+ local ang2 = body2:getAngle()
+ test:assertRange(ang2, -0.1, 0, 'check angle after 2')
+ test:assertRange(vel2, 1, 2, 'check velocity after 2')
+
+ -- apply linear impulse
+ body2:applyLinearImpulse(-4, -59)
+ world:update(1)
+ local ang3 = body2:getAngle()
+ local vel3 = body2:getAngularVelocity()
+ test:assertRange(ang3, -2, -1, 'check angle after 3')
+ test:assertRange(vel3, 0, 1, 'check velocity after 3')
+
+ -- apply torque
+ body2:applyTorque(4)
+ world:update(2)
+ local ang4 = body2:getAngle()
+ local vel4 = body2:getAngularVelocity()
+ test:assertRange(ang4, -1, 0, 'check angle after 4')
+ test:assertRange(vel4, 0, 1, 'check velocity after 4')
+
+ -- check destroy
+ test:assertFalse(body1:isDestroyed(), 'check not destroyed')
+ body1:destroy()
+ test:assertTrue(body1:isDestroyed(), 'check destroyed')
+
+end
+
+
+-- Contact (love.physics.World:getContacts)
+love.test.physics.Contact = function(test)
+
+ -- create a setup so we can access some contact objects
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 0, 0, 'dynamic')
+ local body2 = love.physics.newBody(world, 10, 10, 'dynamic')
+ local rectangle1 = love.physics.newRectangleShape(body1, 0, 0, 10, 10)
+ local rectangle2 = love.physics.newRectangleShape(body2, 0, 0, 10, 10)
+ rectangle1:setUserData('rec1')
+ rectangle2:setUserData('rec2')
+
+ -- used to check for collisions + no. of collisions
+ local collided = false
+ local pass = 1
+
+ -- set callback for contact start
+ world:setCallbacks(
+ function(shape_a, shape_b, contact)
+ collided = true
+
+ -- check contact object
+ test:assertObject(contact)
+
+ -- check child indices
+ local indexA, indexB = contact:getChildren()
+ test:assertEquals(1, indexA, 'check child indice a')
+ test:assertEquals(1, indexB, 'check child indice b')
+
+ -- check shapes match using userdata
+ local shapeA, shapeB = contact:getShapes()
+ test:assertEquals(shape_a:getUserData(), shapeA:getUserData(), 'check shape a matches')
+ test:assertEquals(shape_b:getUserData(), shapeB:getUserData(), 'check shape b matches')
+
+ -- check normal pos
+ local nx, ny = contact:getNormal()
+ test:assertEquals(1, nx, 'check normal x')
+ test:assertEquals(0, ny, 'check normal y')
+
+ -- check actual pos
+ local px1, py1, px2, py2 = contact:getPositions()
+ test:assertRange(px1, 5, 6, 'check collide x 1')
+ test:assertRange(py1, 5, 6, 'check collide y 1')
+ test:assertRange(px2, 5, 6, 'check collide x 2')
+ test:assertRange(py2, 5, 6, 'check collide y 2')
+
+ -- check touching
+ test:assertTrue(contact:isTouching(), 'check touching')
+
+ -- check enabled (we pass through twice to test on/off)
+ test:assertEquals(pass == 1, contact:isEnabled(), 'check enabled for pass ' .. tostring(pass))
+
+ -- check friction
+ test:assertRange(contact:getFriction(), 0.2, 0.3, 'check def friction')
+ contact:setFriction(0.1)
+ test:assertRange(contact:getFriction(), 0.1, 0.2, 'check set friction')
+ contact:resetFriction()
+ test:assertRange(contact:getFriction(), 0.2, 0.3, 'check reset friction')
+
+ -- check restitution
+ test:assertEquals(0, contact:getRestitution(), 'check def restitution')
+ contact:setRestitution(1)
+ test:assertEquals(1, contact:getRestitution(), 'check set restitution')
+ contact:resetRestitution()
+ test:assertEquals(0, contact:getRestitution(), 'check reset restitution')
+ pass = pass + 1
+
+ end, function() end, function(shape_a, shape_b, contact)
+ if pass > 2 then
+ contact:setEnabled(false)
+ end
+ end, function() end
+ )
+
+ -- check bodies collided
+ world:update(1)
+ test:assertTrue(collided, 'check bodies collided')
+
+ -- update again for enabled check
+ world:update(1)
+ test:assertEquals(2, pass, 'check ran twice')
+
+end
+
+
+-- Joint (love.physics.newDistanceJoint)
+love.test.physics.Joint = function(test)
+
+ -- make joint
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'dynamic')
+ local body2 = love.physics.newBody(world, 20, 20, 'dynamic')
+ local joint = love.physics.newDistanceJoint(body1, body2, 10, 10, 20, 20, true)
+ test:assertObject(joint)
+
+ -- check type
+ test:assertEquals('distance', joint:getType(), 'check joint type')
+
+ -- check not destroyed
+ test:assertFalse(joint:isDestroyed(), 'check not destroyed')
+
+ -- check reaction props (sometimes nil on linux runners)
+ if joint:getReactionForce(1) ~= nil then
+ test:assertEquals(0, joint:getReactionForce(1), 'check reaction force')
+ end
+ if joint:getReactionTorque(1) ~= nil then
+ test:assertEquals(0, joint:getReactionTorque(1), 'check reaction torque')
+ end
+
+ -- check body pointer
+ local b1, b2 = joint:getBodies()
+ test:assertEquals(body1:getX(), b1:getX(), 'check body 1')
+ test:assertEquals(body2:getX(), b2:getX(), 'check body 2')
+
+ -- check joint anchors
+ local x1, y1, x2, y2 = joint:getAnchors()
+ test:assertRange(x1, 10, 11, 'check anchor x1')
+ test:assertRange(y1, 10, 11, 'check anchor y1')
+ test:assertRange(x2, 20, 21, 'check anchor x2')
+ test:assertRange(y2, 20, 21, 'check anchor y2')
+ test:assertTrue(joint:getCollideConnected(), 'check not colliding')
+
+ -- test userdata
+ test:assertEquals(nil, joint:getUserData(), 'check no data by def')
+ joint:setUserData('hello')
+ test:assertEquals('hello', joint:getUserData(), 'check set userdata')
+
+ -- destroy
+ joint:destroy()
+ test:assertTrue(joint:isDestroyed(), 'check destroyed')
+
+end
+
+
+-- Shape (love.physics.newCircleShape)
+-- @NOTE in 12.0 fixtures have been merged into shapes
+love.test.physics.Shape = function(test)
+
+ -- create shape
+ local world = love.physics.newWorld(0, 0, false)
+ local body1 = love.physics.newBody(world, 0, 0, 'dynamic')
+ local shape1 = love.physics.newRectangleShape(body1, 5, 5, 10, 10)
+ test:assertObject(shape1)
+
+ -- check child count
+ test:assertEquals(1, shape1:getChildCount(), 'check child count')
+
+ -- check radius
+ test:assertRange(shape1:getRadius(), 0, 0.4, 'check radius')
+
+ -- check type match
+ test:assertEquals('polygon', shape1:getType(), 'check rectangle type')
+
+ -- check body pointer
+ test:assertEquals(0, shape1:getBody():getX(), 'check body link')
+
+ -- check category
+ test:assertEquals(1, shape1:getCategory(), 'check def category')
+ shape1:setCategory(3, 5, 6)
+ local categories = {shape1:getCategory()}
+ test:assertEquals(14, categories[1] + categories[2] + categories[3], 'check set category')
+
+ -- check sensor prop
+ test:assertFalse(shape1:isSensor(), 'check sensor def')
+ shape1:setSensor(true)
+ test:assertTrue(shape1:isSensor(), 'check set sensor')
+ shape1:setSensor(false)
+
+ -- check not destroyed
+ test:assertFalse(shape1:isDestroyed(), 'check not destroyed')
+
+ -- check user data
+ test:assertEquals(nil, shape1:getUserData(), 'check no user data')
+ shape1:setUserData({ test = 14 })
+ test:assertEquals(14, shape1:getUserData().test, 'check user data set')
+
+ -- check bounding box
+ -- polygons have an additional skin radius to help with collisions
+ -- so this wont be 0, 0, 10, 10 as you'd think but has an additional 0.3 padding
+ local topLeftX, topLeftY, bottomRightX, bottomRightY = shape1:computeAABB(0, 0, 0, 1)
+ local tlx, tly, brx, bry = shape1:getBoundingBox(1)
+ test:assertEquals(topLeftX, tlx, 'check bbox methods match tlx')
+ test:assertEquals(topLeftY, tly, 'check bbox methods match tly')
+ test:assertEquals(bottomRightX, brx, 'check bbox methods match brx')
+ test:assertEquals(bottomRightY, bry, 'check bbox methods match bry')
+ test:assertEquals(topLeftX, topLeftY, 'check bbox tl 1')
+ test:assertRange(topLeftY, -0.3, -0.2, 'check bbox tl 2')
+ test:assertEquals(bottomRightX, bottomRightY, 'check bbox br 1')
+ test:assertRange(bottomRightX, 10.3, 10.4, 'check bbox br 2')
+
+ -- check density
+ test:assertEquals(1, shape1:getDensity(), 'check def density')
+ shape1:setDensity(5)
+ test:assertEquals(5, shape1:getDensity(), 'check set density')
+
+ -- check mass
+ local x1, y1, mass1, inertia1 = shape1:getMassData()
+ test:assertRange(x1, 5, 5.1, 'check shape mass pos x')
+ test:assertRange(y1, 5, 5.1, 'check shape mass pos y')
+ test:assertRange(mass1, 0.5, 0.6, 'check mass at 1 density')
+ test:assertRange(inertia1, 0, 0.1, 'check intertia at 1 density')
+ local x2, y2, mass2, inertia2 = shape1:computeMass(1000)
+ test:assertRange(mass2, 111, 112, 'check mass at 1000 density')
+ test:assertRange(inertia2, 7407, 7408, 'check intertia at 1000 density')
+
+ -- check friction
+ test:assertRange(shape1:getFriction(), 0.2, 0.3, 'check def friction')
+ shape1:setFriction(1)
+ test:assertEquals(1, shape1:getFriction(), 'check set friction')
+
+ -- check restitution
+ test:assertEquals(0, shape1:getRestitution(), 'check def restitution')
+ shape1:setRestitution(0.5)
+ test:assertRange(shape1:getRestitution(), 0.5, 0.6, 'check set restitution')
+
+ -- check points
+ local bodyp = love.physics.newBody(world, 0, 0, 'dynamic')
+ local shape2 = love.physics.newRectangleShape(bodyp, 5, 5, 10, 10)
+ test:assertTrue(shape2:testPoint(5, 5), 'check point 5,5')
+ test:assertTrue(shape2:testPoint(10, 10, 0, 15, 15), 'check point 15,15 after translate 10,10')
+ test:assertFalse(shape2:testPoint(5, 5, 90, 10, 10), 'check point 10,10 after translate 5,5,90')
+ test:assertFalse(shape2:testPoint(10, 10, 90, 5, 5), 'check point 5,5 after translate 10,10,90')
+ test:assertFalse(shape2:testPoint(15, 15), 'check point 15,15')
+
+ -- check ray cast
+ local xn1, yn1, fraction1 = shape2:rayCast(-20, -20, 20, 20, 100, 0, 0, 0, 1)
+ test:assertNotEquals(nil, xn1, 'check ray 1 x')
+ test:assertNotEquals(nil, xn1, 'check ray 1 y')
+ local xn2, yn2, fraction2 = shape2:rayCast(10, 10, -150, -150, 100, 0, 0, 0, 1)
+ test:assertEquals(nil, xn2, 'check ray 2 x')
+ test:assertEquals(nil, yn2, 'check ray 2 y')
+
+ -- check filtering
+ test:assertEquals(nil, shape2:getMask(), 'check no mask')
+ shape2:setMask(1, 2, 3)
+ test:assertEquals(3, #{shape2:getMask()}, 'check set mask')
+ test:assertEquals(0, shape2:getGroupIndex(), 'check no index')
+ shape2:setGroupIndex(-1)
+ test:assertEquals(-1, shape2:getGroupIndex(), 'check set index')
+ local cat, mask, group = shape2:getFilterData()
+ test:assertEquals(1, cat, 'check filter cat')
+ test:assertEquals(65528, mask, 'check filter mask')
+ test:assertEquals(-1, group, 'check filter group')
+
+ -- check destroyed
+ shape1:destroy()
+ test:assertTrue(shape1:isDestroyed(), 'check destroyed')
+ shape2:destroy()
+
+ -- run some collision checks using filters, setup new shapes
+ local body2 = love.physics.newBody(world, 5, 5, 'dynamic')
+ local shape3 = love.physics.newRectangleShape(body1, 0, 0, 10, 10)
+ local shape4 = love.physics.newRectangleShape(body2, 0, 0, 10, 10)
+ local collisions = 0
+ world:setCallbacks(
+ function() collisions = collisions + 1 end,
+ function() end,
+ function() end,
+ function() end
+ )
+
+ -- same positive group do collide
+ shape3:setGroupIndex(1)
+ shape4:setGroupIndex(1)
+ world:update(1)
+ test:assertEquals(1, collisions, 'check positive group collide')
+
+ -- check negative group dont collide
+ shape3:setGroupIndex(-1)
+ shape4:setGroupIndex(-1)
+ body2:setPosition(20, 20); world:update(1); body2:setPosition(0, 0); world:update(1)
+ test:assertEquals(1, collisions, 'check negative group collide')
+
+ -- check masks do collide
+ shape3:setGroupIndex(0)
+ shape4:setGroupIndex(0)
+ shape3:setCategory(2)
+ shape4:setMask(3)
+ body2:setPosition(20, 20); world:update(1); body2:setPosition(0, 0); world:update(1)
+ test:assertEquals(2, collisions, 'check mask collide')
+
+ -- check masks not colliding
+ shape3:setCategory(2)
+ shape4:setMask(2, 4, 6)
+ body2:setPosition(20, 20); world:update(1); body2:setPosition(0, 0); world:update(1)
+ test:assertEquals(2, collisions, 'check mask not collide')
+
+end
+
+
+-- World (love.physics.newWorld)
+love.test.physics.World = function(test)
+
+ -- create new world
+ local world = love.physics.newWorld(0, 0, false)
+ local body1 = love.physics.newBody(world, 0, 0, 'dynamic')
+ local rectangle1 = love.physics.newRectangleShape(body1, 0, 0, 10, 10)
+ test:assertObject(world)
+
+ -- check bodies in world
+ test:assertEquals(1, #world:getBodies(), 'check 1 body')
+ test:assertEquals(0, world:getBodies()[1]:getX(), 'check body prop x')
+ test:assertEquals(0, world:getBodies()[1]:getY(), 'check body prop y')
+ world:translateOrigin(-10, -10) -- check affects bodies
+ test:assertRange(world:getBodies()[1]:getX(), 9, 11, 'check body prop change x')
+ test:assertRange(world:getBodies()[1]:getY(), 9, 11, 'check body prop change y')
+ test:assertEquals(1, world:getBodyCount(), 'check 1 body count')
+
+ -- check world status
+ test:assertFalse(world:isLocked(), 'check not updating')
+ test:assertFalse(world:isSleepingAllowed(), 'check no sleep (till brooklyn)')
+ world:setSleepingAllowed(true)
+ test:assertTrue(world:isSleepingAllowed(), 'check can sleep')
+
+ -- check world objects
+ test:assertEquals(0, #world:getJoints(), 'check no joints')
+ test:assertEquals(0, world:getJointCount(), 'check no joints count')
+ test:assertEquals(0, world:getGravity(), 'check def gravity')
+ test:assertEquals(0, #world:getContacts(), 'check no contacts')
+ test:assertEquals(0, world:getContactCount(), 'check no contact count')
+
+ -- check callbacks are called
+ local beginContact, endContact, preSolve, postSolve = world:getCallbacks()
+ test:assertEquals(nil, beginContact, 'check no begin contact callback')
+ test:assertEquals(nil, endContact, 'check no end contact callback')
+ test:assertEquals(nil, preSolve, 'check no pre solve callback')
+ test:assertEquals(nil, postSolve, 'check no post solve callback')
+ local beginContactCheck = false
+ local endContactCheck = false
+ local preSolveCheck = false
+ local postSolveCheck = false
+ local collisions = 0
+ world:setCallbacks(
+ function() beginContactCheck = true; collisions = collisions + 1 end,
+ function() endContactCheck = true end,
+ function() preSolveCheck = true end,
+ function() postSolveCheck = true end
+ )
+
+ -- setup so we can collide stuff to call the callbacks
+ local body2 = love.physics.newBody(world, 10, 10, 'dynamic')
+ local rectangle2 = love.physics.newRectangleShape(body2, 0, 0, 10, 10)
+ test:assertFalse(beginContactCheck, 'check world didnt update after adding body')
+ world:update(1)
+ test:assertTrue(beginContactCheck, 'check contact start')
+ test:assertTrue(preSolveCheck, 'check pre solve')
+ test:assertTrue(postSolveCheck, 'check post solve')
+ body2:setPosition(100, 100)
+ world:update(1)
+ test:assertTrue(endContactCheck, 'check contact end')
+
+ -- check point checking
+ local shapes = 0
+ world:queryShapesInArea(0, 0, 10, 10, function(x)
+ shapes = shapes + 1
+ end)
+ test:assertEquals(1, shapes, 'check shapes in area')
+
+ -- check raycast
+ world:rayCast(0, 0, 200, 200, function(x)
+ shapes = shapes + 1
+ return 1
+ end)
+ test:assertEquals(3, shapes, 'check shapes in raycast')
+
+ -- change collision logic
+ test:assertEquals(nil, world:getContactFilter(), 'check def filter')
+ world:update(1)
+ world:setContactFilter(function(s1, s2)
+ return false -- nothing collides
+ end)
+ body2:setPosition(10, 10)
+ world:update(1)
+ test:assertEquals(1, collisions, 'check collision logic change')
+
+ -- check gravity
+ world:setGravity(1, 1)
+ test:assertEquals(1, world:getGravity(), 'check grav change')
+
+ -- check destruction
+ test:assertFalse(world:isDestroyed(), 'check not destroyed')
+ world:destroy()
+ test:assertTrue(world:isDestroyed(), 'check world destroyed')
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.physics.getDistance
+love.test.physics.getDistance = function(test)
+ -- setup two fixtues to check
+ local world = love.physics.newWorld(0, 0, false)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ local shape1 = love.physics.newEdgeShape(body, 0, 0, 5, 5)
+ local shape2 = love.physics.newEdgeShape(body, 10, 10, 15, 15)
+ -- check distance between them
+ test:assertRange(love.physics.getDistance(shape1, shape2), 6, 7, 'check distance matches')
+end
+
+
+-- love.physics.getMeter
+love.test.physics.getMeter = function(test)
+ -- check value set is returned
+ love.physics.setMeter(30)
+ test:assertEquals(30, love.physics.getMeter(), 'check meter matches')
+end
+
+
+-- love.physics.newBody
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newBody = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ test:assertObject(body)
+end
+
+
+-- love.physics.newChainShape
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newChainShape = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ test:assertObject(love.physics.newChainShape(body, true, 0, 0, 1, 0, 1, 1, 0, 1))
+end
+
+
+-- love.physics.newCircleShape
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newCircleShape = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ test:assertObject(love.physics.newCircleShape(body, 10))
+end
+
+
+-- love.physics.newDistanceJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newDistanceJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newDistanceJoint(body1, body2, 10, 10, 20, 20, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newEdgeShape
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newEdgeShape = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ local obj = love.physics.newEdgeShape(body, 0, 0, 10, 10)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newFrictionJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newFrictionJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newFrictionJoint(body1, body2, 15, 15, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newGearJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newGearJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'dynamic')
+ local body2 = love.physics.newBody(world, 20, 20, 'dynamic')
+ local body3 = love.physics.newBody(world, 30, 30, 'dynamic')
+ local body4 = love.physics.newBody(world, 40, 40, 'dynamic')
+ local joint1 = love.physics.newPrismaticJoint(body1, body2, 10, 10, 20, 20, true)
+ local joint2 = love.physics.newPrismaticJoint(body3, body4, 30, 30, 40, 40, true)
+ local obj = love.physics.newGearJoint(joint1, joint2, 1, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newMotorJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newMotorJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newMotorJoint(body1, body2, 1)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newMouseJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newMouseJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ local obj = love.physics.newMouseJoint(body, 10, 10)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newPolygonShape
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newPolygonShape = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ local obj = love.physics.newPolygonShape(body, {0, 0, 2, 3, 2, 1, 3, 1, 5, 1})
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newPrismaticJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newPrismaticJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newPrismaticJoint(body1, body2, 10, 10, 20, 20, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newPulleyJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newPulleyJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newPulleyJoint(body1, body2, 10, 10, 20, 20, 15, 15, 25, 25, 1, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newRectangleShape
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newRectangleShape = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body = love.physics.newBody(world, 10, 10, 'static')
+ local shape1 = love.physics.newRectangleShape(body, 10, 20)
+ local shape2 = love.physics.newRectangleShape(body, 10, 10, 40, 30, 10)
+ test:assertObject(shape1)
+ test:assertObject(shape2)
+end
+
+
+-- love.physics.newRevoluteJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newRevoluteJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newRevoluteJoint(body1, body2, 10, 10, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newRopeJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newRopeJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newRopeJoint(body1, body2, 10, 10, 20, 20, 50, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newWeldJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newWeldJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newWeldJoint(body1, body2, 10, 10, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newWheelJoint
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newWheelJoint = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ local body1 = love.physics.newBody(world, 10, 10, 'static')
+ local body2 = love.physics.newBody(world, 20, 20, 'static')
+ local obj = love.physics.newWheelJoint(body1, body2, 10, 10, 5, 5, true)
+ test:assertObject(obj)
+end
+
+
+-- love.physics.newWorld
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.physics.newWorld = function(test)
+ local world = love.physics.newWorld(1, 1, true)
+ test:assertObject(world)
+end
+
+
+-- love.physics.setMeter
+love.test.physics.setMeter = function(test)
+ -- set initial meter
+ local world = love.physics.newWorld(1, 1, true)
+ love.physics.setMeter(30)
+ local body = love.physics.newBody(world, 300, 300, "dynamic")
+ -- check changing meter changes pos value relatively
+ love.physics.setMeter(10)
+ local x, y = body:getPosition()
+ test:assertEquals(100, x, 'check pos x')
+ test:assertEquals(100, y, 'check pos y')
+end
diff --git a/testing/tests/sound.lua b/testing/tests/sound.lua
new file mode 100644
index 000000000..0304d0562
--- /dev/null
+++ b/testing/tests/sound.lua
@@ -0,0 +1,106 @@
+-- love.sound
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------OBJECTS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- Decoder (love.sound.newDecoder)
+love.test.sound.Decoder = function(test)
+
+ -- create obj
+ local decoder = love.sound.newDecoder('resources/click.ogg')
+ test:assertObject(decoder)
+
+ -- check bit depth
+ test:assertMatch({8, 16}, decoder:getBitDepth(), 'check bit depth')
+
+ -- check channel count
+ test:assertMatch({1, 2}, decoder:getChannelCount(), 'check channel count')
+
+ -- check duration
+ test:assertRange(decoder:getDuration(), 0.06, 0.07, 'check duration')
+
+ -- check sample rate
+ test:assertEquals(44100, decoder:getSampleRate(), 'check sample rate')
+
+ -- check makes sound data (test in method below)
+ test:assertObject(decoder:decode())
+
+ -- check cloning sound
+ local clone = decoder:clone()
+ test:assertMatch({8, 16}, clone:getBitDepth(), 'check cloned bit depth')
+ test:assertMatch({1, 2}, clone:getChannelCount(), 'check cloned channel count')
+ test:assertRange(clone:getDuration(), 0.06, 0.07, 'check cloned duration')
+ test:assertEquals(44100, clone:getSampleRate(), 'check cloned sample rate')
+
+end
+
+
+-- SoundData (love.sound.newSoundData)
+love.test.sound.SoundData = function(test)
+
+ -- create obj
+ local sdata = love.sound.newSoundData('resources/click.ogg')
+ test:assertObject(sdata)
+
+ -- check data size + string
+ test:assertEquals(11708, sdata:getSize(), 'check size')
+ test:assertNotNil(sdata:getString())
+
+ -- check bit depth
+ test:assertMatch({8, 16}, sdata:getBitDepth(), 'check bit depth')
+
+ -- check channel count
+ test:assertMatch({1, 2}, sdata:getChannelCount(), 'check channel count')
+
+ -- check duration
+ test:assertRange(sdata:getDuration(), 0.06, 0.07, 'check duration')
+
+ -- check samples
+ test:assertEquals(44100, sdata:getSampleRate(), 'check sample rate')
+ test:assertEquals(2927, sdata:getSampleCount(), 'check sample count')
+
+ -- check cloning
+ local clone = sdata:clone()
+ test:assertEquals(11708, clone:getSize(), 'check clone size')
+ test:assertNotNil(clone:getString())
+ test:assertMatch({8, 16}, clone:getBitDepth(), 'check clone bit depth')
+ test:assertMatch({1, 2}, clone:getChannelCount(), 'check clone channel count')
+ test:assertRange(clone:getDuration(), 0.06, 0.07, 'check clone duration')
+ test:assertEquals(44100, clone:getSampleRate(), 'check clone sample rate')
+ test:assertEquals(2927, clone:getSampleCount(), 'check clone sample count')
+
+ -- check sample setting
+ test:assertRange(sdata:getSample(0.001), -0.1, 0, 'check sample 1')
+ test:assertRange(sdata:getSample(0.005), -0.1, 0, 'check sample 1')
+ sdata:setSample(0.002, 1)
+ test:assertEquals(1, sdata:getSample(0.002), 'check setting sample manually')
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+------------------------------------METHODS-------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+
+-- love.sound.newDecoder
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.sound.newDecoder = function(test)
+ test:assertObject(love.sound.newDecoder('resources/click.ogg'))
+end
+
+
+-- love.sound.newSoundData
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.sound.newSoundData = function(test)
+ test:assertObject(love.sound.newSoundData('resources/click.ogg'))
+ test:assertObject(love.sound.newSoundData(math.floor((1/32)*44100), 44100, 16, 1))
+end
diff --git a/testing/tests/system.lua b/testing/tests/system.lua
new file mode 100644
index 000000000..e8ef4229c
--- /dev/null
+++ b/testing/tests/system.lua
@@ -0,0 +1,83 @@
+-- love.system
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------METHODS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.system.getClipboardText
+love.test.system.getClipboardText = function(test)
+ -- ignore if not using window
+ if love.test.windowmode == false then
+ return test:skipTest('clipboard only available in window mode')
+ end
+ -- check clipboard value is set
+ love.system.setClipboardText('helloworld')
+ test:assertEquals('helloworld', love.system.getClipboardText(), 'check clipboard match')
+end
+
+
+-- love.system.getOS
+love.test.system.getOS = function(test)
+ -- check os is in documented values
+ local os = love.system.getOS()
+ local options = {'OS X', 'Windows', 'Linux', 'Android', 'iOS'}
+ test:assertMatch(options, os, 'check value matches')
+end
+
+
+-- love.system.getPowerInfo
+love.test.system.getPowerInfo = function(test)
+ -- check battery state is one of the documented states
+ local state, percent, seconds = love.system.getPowerInfo()
+ local states = {'unknown', 'battery', 'nobattery', 'charging', 'charged'}
+ test:assertMatch(states, state, 'check value matches')
+ -- if percent/seconds check within expected range
+ if percent ~= nil then
+ test:assertRange(percent, 0, 100, 'check battery percent within range')
+ end
+ if seconds ~= nil then
+ test:assertNotNil(seconds)
+ end
+end
+
+
+-- love.system.getProcessorCount
+love.test.system.getProcessorCount = function(test)
+ test:assertNotNil(love.system.getProcessorCount()) -- youd hope right
+end
+
+
+-- love.system.hasBackgroundMusic
+love.test.system.hasBackgroundMusic = function(test)
+ test:assertNotNil(love.system.hasBackgroundMusic())
+end
+
+
+-- love.system.openURL
+love.test.system.openURL = function(test)
+ test:skipTest('cant test this worked')
+ --test:assertNotEquals(nil, love.system.openURL('https://love2d.org'), 'check open URL')
+end
+
+
+-- love.system.getClipboardText
+love.test.system.setClipboardText = function(test)
+ -- ignore if not using window
+ if love.test.windowmode == false then
+ return test:skipTest('clipboard only available in window mode')
+ end
+ -- check value returned is what was set
+ love.system.setClipboardText('helloworld')
+ test:assertEquals('helloworld', love.system.getClipboardText(), 'check set text')
+end
+
+
+-- love.system.vibrate
+-- @NOTE cant really test this
+love.test.system.vibrate = function(test)
+ test:skipTest('cant test this worked')
+end
diff --git a/testing/tests/thread.lua b/testing/tests/thread.lua
new file mode 100644
index 000000000..6bf03d358
--- /dev/null
+++ b/testing/tests/thread.lua
@@ -0,0 +1,137 @@
+-- love.thread
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------OBJECTS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- Channel (love.thread.newChannel)
+love.test.thread.Channel = function(test)
+
+ -- create channel
+ local channel = love.thread.getChannel('test')
+ test:assertObject(channel)
+
+ -- setup thread to use
+ local threadcode1 = [[
+ require("love.timer")
+ love.timer.sleep(0.1)
+ love.thread.getChannel('test'):push('hello world')
+ love.timer.sleep(0.1)
+ love.thread.getChannel('test'):push('me again')
+ ]]
+ local thread1 = love.thread.newThread(threadcode1)
+ thread1:start()
+
+ -- check message sent from thread to channel
+ local msg1 = channel:demand()
+ test:assertEquals('hello world', msg1, 'check 1st message was sent')
+ thread1:wait()
+ test:assertEquals(1, channel:getCount(), 'check still another message')
+ test:assertEquals('me again', channel:peek(), 'check 2nd message pending')
+ local msg2 = channel:pop()
+ test:assertEquals('me again', msg2, 'check 2nd message was sent')
+ channel:clear()
+
+ -- setup another thread for some ping pong
+ local threadcode2 = [[
+ local function setChannel(channel, value)
+ channel:clear()
+ return channel:push(value)
+ end
+ local channel = love.thread.getChannel('test')
+ local waiting = true
+ local sent = nil
+ while waiting == true do
+ if sent == nil then
+ sent = channel:performAtomic(setChannel, 'ping')
+ end
+ if channel:hasRead(sent) then
+ local msg = channel:demand()
+ if msg == 'pong' then
+ channel:push(msg)
+ waiting = false
+ end
+ end
+ end
+ ]]
+
+ -- first we run a thread that will send 1 ping
+ local thread2 = love.thread.newThread(threadcode2)
+ thread2:start()
+
+ -- we wait for that ping to be sent and then send a pong back
+ local msg3 = channel:demand()
+ test:assertEquals('ping', msg3, 'check message recieved 1')
+
+ -- thread should be waiting for us, and checking is the ping was read
+ channel:supply('pong', 1)
+
+ -- if it was then it should send back our pong and thread should die
+ thread2:wait()
+ local msg4 = channel:pop()
+ test:assertEquals('pong', msg4, 'check message recieved 2')
+ test:assertEquals(0, channel:getCount())
+
+end
+
+
+-- Thread (love.thread.newThread)
+love.test.thread.Thread = function(test)
+
+ -- create thread
+ local threadcode = [[
+ local b = 0
+ for a=1,100000 do
+ b = b + a
+ end
+ ]]
+ local thread = love.thread.newThread(threadcode)
+ test:assertObject(thread)
+
+ -- check thread runs
+ thread:start()
+ test:assertTrue(thread:isRunning(), 'check started')
+ thread:wait()
+ test:assertFalse(thread:isRunning(), 'check finished')
+ test:assertEquals(nil, thread:getError(), 'check no errors')
+
+ -- check an invalid thread
+ local badthreadcode = 'local b = 0\nreturn b + "string" .. 10'
+ local badthread = love.thread.newThread(badthreadcode)
+ badthread:start()
+ badthread:wait()
+ test:assertNotNil(badthread:getError())
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------METHODS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.thread.getChannel
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.thread.getChannel = function(test)
+ test:assertObject(love.thread.getChannel('test'))
+end
+
+
+-- love.thread.newChannel
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.thread.newChannel = function(test)
+ test:assertObject(love.thread.newChannel())
+end
+
+
+-- love.thread.newThread
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.thread.newThread = function(test)
+ test:assertObject(love.thread.newThread('classes/TestSuite.lua'))
+end
diff --git a/testing/tests/timer.lua b/testing/tests/timer.lua
new file mode 100644
index 000000000..a41f7e0dd
--- /dev/null
+++ b/testing/tests/timer.lua
@@ -0,0 +1,52 @@
+-- love.timer
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------METHODS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.timer.getAverageDelta
+-- @NOTE not sure if you could reliably get a specific delta?
+love.test.timer.getAverageDelta = function(test)
+ test:assertNotNil(love.timer.getAverageDelta())
+end
+
+-- love.timer.getDelta
+-- @NOTE not sure if you could reliably get a specific delta?
+love.test.timer.getDelta = function(test)
+ test:assertNotNil(love.timer.getDelta())
+end
+
+
+-- love.timer.getFPS
+-- @NOTE not sure if you could reliably get a specific FPS?
+love.test.timer.getFPS = function(test)
+ test:assertNotNil(love.timer.getFPS())
+end
+
+
+-- love.timer.getTime
+love.test.timer.getTime = function(test)
+ local starttime = love.timer.getTime()
+ love.timer.sleep(1)
+ local endtime = love.timer.getTime() - starttime
+ test:assertRange(endtime, 1, 2, 'check 1s passes')
+end
+
+
+-- love.timer.sleep
+love.test.timer.sleep = function(test)
+ local starttime = love.timer.getTime()
+ love.timer.sleep(1)
+ test:assertRange(love.timer.getTime() - starttime, 1, 2, 'check 1s passes')
+end
+
+
+-- love.timer.step
+-- @NOTE not sure if you could reliably get a specific step val?
+love.test.timer.step = function(test)
+ test:assertNotNil(love.timer.step())
+end
diff --git a/testing/tests/video.lua b/testing/tests/video.lua
new file mode 100644
index 000000000..9bcbf4b31
--- /dev/null
+++ b/testing/tests/video.lua
@@ -0,0 +1,46 @@
+-- love.video
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------OBJECTS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- VideoStream (love.thread.newVideoStream)
+love.test.video.VideoStream = function(test)
+
+ -- create obj
+ local video = love.video.newVideoStream('resources/sample.ogv')
+ test:assertObject(video)
+
+ -- check def properties
+ test:assertEquals('resources/sample.ogv', video:getFilename(), 'check filename')
+ test:assertFalse(video:isPlaying(), 'check not playing by def')
+
+ -- check playing and pausing states
+ video:play()
+ test:assertTrue(video:isPlaying(), 'check now playing')
+ video:seek(0.3)
+ test:assertRange(video:tell(), 0.3, 0.4, 'check seek/tell')
+ video:rewind()
+ test:assertEquals(0, video:tell(), 'check rewind')
+ video:pause()
+ test:assertFalse(video:isPlaying(), 'check paused')
+
+end
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------METHODS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.video.newVideoStream
+-- @NOTE this is just basic nil checking, objs have their own test method
+love.test.video.newVideoStream = function(test)
+ test:assertObject(love.video.newVideoStream('resources/sample.ogv'))
+end
diff --git a/testing/tests/window.lua b/testing/tests/window.lua
new file mode 100644
index 000000000..bc2dbed1a
--- /dev/null
+++ b/testing/tests/window.lua
@@ -0,0 +1,371 @@
+-- love.window
+
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+----------------------------------METHODS---------------------------------------
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+
+-- love.window.close
+love.test.window.close = function(test)
+ -- closing window should cause graphics to not be active
+ love.window.close()
+ local active = love.graphics.isActive()
+ test:assertFalse(active, 'check window not active')
+ love.window.updateMode(360, 240) -- reset
+ active = love.graphics.isActive()
+ test:assertTrue(active, 'check window active again')
+end
+
+-- love.window.fromPixels
+love.test.window.fromPixels = function(test)
+ -- check dpi/pixel ratio as expected
+ local dpi = love.window.getDPIScale()
+ local pixels = love.window.fromPixels(100)
+ test:assertEquals(100/dpi, pixels, 'check dpi ratio')
+end
+
+
+-- love.window.getDPIScale
+-- @NOTE dependent on hardware so best can do is not nil
+love.test.window.getDPIScale = function(test)
+ test:assertNotNil(test)
+end
+
+
+-- love.window.getDesktopDimensions
+-- @NOTE dependent on hardware so best can do is not nil
+love.test.window.getDesktopDimensions = function(test)
+ local w, h = love.window.getDesktopDimensions()
+ test:assertNotNil(w)
+ test:assertNotNil(h)
+end
+
+
+-- love.window.getDisplayCount
+-- @NOTE cant wait for the test suite to be run headless and fail here
+love.test.window.getDisplayCount = function(test)
+ test:assertGreaterEqual(1, love.window.getDisplayCount(), 'check 1 display')
+end
+
+
+-- love.window.getDisplayName
+-- @NOTE dependent on hardware so best can do is not nil
+love.test.window.getDisplayName = function(test)
+ test:assertNotNil(love.window.getDisplayName(1))
+end
+
+
+-- love.window.getDisplayOrientation
+-- @NOTE dependent on hardware so best can do is not nil
+love.test.window.getDisplayOrientation = function(test)
+ test:assertNotNil(love.window.getDisplayOrientation(1))
+end
+
+
+-- love.window.getFullscreen
+love.test.window.getFullscreen = function(test)
+ -- check not fullscreen to start
+ test:assertFalse(love.window.getFullscreen(), 'check not fullscreen')
+ love.window.setFullscreen(true)
+ -- check now fullscreen
+ test:assertTrue(love.window.getFullscreen(), 'check now fullscreen')
+ love.window.setFullscreen(false) -- reset
+end
+
+
+-- love.window.getFullscreenModes
+-- @NOTE dependent on hardware so best can do is not nil
+love.test.window.getFullscreenModes = function(test)
+ test:assertNotNil(love.window.getFullscreenModes(1))
+end
+
+
+-- love.window.getIcon
+love.test.window.getIcon = function(test)
+ -- check icon nil by default if not set
+ test:assertEquals(nil, love.window.getIcon(), 'check nil by default')
+ local icon = love.image.newImageData('resources/love.png')
+ -- check getting icon not nil after setting
+ love.window.setIcon(icon)
+ test:assertNotNil(love.window.getIcon())
+end
+
+
+-- love.window.getMode
+-- @NOTE could prob add more checks on the flags here based on conf.lua
+love.test.window.getMode = function(test)
+ local w, h, flags = love.window.getMode()
+ test:assertEquals(360, w, 'check w')
+ test:assertEquals(240, h, 'check h')
+ test:assertFalse(flags["fullscreen"], 'check fullscreen')
+end
+
+
+-- love.window.getPosition
+-- @NOTE anything we could check display index agaisn't in getPosition return?
+love.test.window.getPosition = function(test)
+ love.window.setPosition(100, 100, 1)
+ local x, y, _ = love.window.getPosition()
+ test:assertEquals(100, x, 'check position x')
+ test:assertEquals(100, y, 'check position y')
+end
+
+
+-- love.window.getSafeArea
+-- @NOTE dependent on hardware so best can do is not nil
+love.test.window.getSafeArea = function(test)
+ local x, y, w, h = love.window.getSafeArea()
+ test:assertNotNil(x)
+ test:assertNotNil(y)
+ test:assertNotNil(w)
+ test:assertNotNil(h)
+end
+
+
+-- love.window.getTitle
+love.test.window.getTitle = function(test)
+ -- check title returned is what was set
+ love.window.setTitle('love.testing')
+ test:assertEquals('love.testing', love.window.getTitle(), 'check title match')
+ love.window.setTitle('love.test')
+end
+
+
+-- love.window.getVSync
+love.test.window.getVSync = function(test)
+ test:assertNotNil(love.window.getVSync())
+end
+
+
+-- love.window.hasFocus
+-- @NOTE cant really test as cant force focus
+love.test.window.hasFocus = function(test)
+ test:assertNotNil(love.window.hasFocus())
+end
+
+
+-- love.window.hasMouseFocus
+-- @NOTE cant really test as cant force focus
+love.test.window.hasMouseFocus = function(test)
+ test:assertNotNil(love.window.hasMouseFocus())
+end
+
+
+-- love.window.isDisplaySleepEnabled
+love.test.window.isDisplaySleepEnabled = function(test)
+ test:assertNotNil(love.window.isDisplaySleepEnabled())
+ -- check disabled
+ love.window.setDisplaySleepEnabled(false)
+ test:assertFalse(love.window.isDisplaySleepEnabled(), 'check sleep disabled')
+ -- check enabled
+ love.window.setDisplaySleepEnabled(true)
+ test:assertTrue(love.window.isDisplaySleepEnabled(), 'check sleep enabled')
+end
+
+
+-- love.window.isMaximized
+love.test.window.isMaximized = function(test)
+ test:assertFalse(love.window.isMaximized(), 'check window not maximized')
+ love.window.maximize()
+ test:waitFrames(10)
+ -- on MACOS maximize wont get recognised immedietely so wait a few frames
+ test:assertTrue(love.window.isMaximized(), 'check window now maximized')
+ love.window.restore()
+end
+
+
+-- love.window.isMinimized
+love.test.window.isMinimized = function(test)
+ -- check not minimized to start
+ test:assertFalse(love.window.isMinimized(), 'check window not minimized')
+ -- try to minimize
+ love.window.minimize()
+ test:waitFrames(10)
+ -- on linux minimize won't get recognized immediately, so wait a few frames
+ test:assertTrue(love.window.isMinimized(), 'check window minimized')
+ love.window.restore()
+end
+
+
+-- love.window.isOpen
+love.test.window.isOpen = function(test)
+ -- check open initially
+ test:assertTrue(love.window.isOpen(), 'check window open')
+ -- try closing
+ love.window.close()
+ test:assertFalse(love.window.isOpen(), 'check window closed')
+ love.window.updateMode(360, 240) -- reset
+end
+
+
+-- love.window.isVisible
+love.test.window.isVisible = function(test)
+ -- check visible initially
+ test:assertTrue(love.window.isVisible(), 'check window visible')
+ -- check closing makes window not visible
+ love.window.close()
+ test:assertFalse(love.window.isVisible(), 'check window not visible')
+ love.window.updateMode(360, 240) -- reset
+end
+
+
+-- love.window.maximize
+love.test.window.maximize = function(test)
+ test:assertFalse(love.window.isMaximized(), 'check window not maximized')
+ -- check maximizing is set
+ love.window.maximize()
+ test:waitFrames(10)
+ -- on macos we need to wait a few frames
+ test:assertTrue(love.window.isMaximized(), 'check window maximized')
+ love.window.restore()
+end
+
+
+-- love.window.minimize
+love.test.window.minimize = function(test)
+ test:assertFalse(love.window.isMinimized(), 'check window not minimized')
+ -- check minimizing is set
+ love.window.minimize()
+ test:waitFrames(10)
+ -- on linux we need to wait a few frames
+ test:assertTrue(love.window.isMinimized(), 'check window maximized')
+ love.window.restore()
+end
+
+
+-- love.window.requestAttention
+love.test.window.requestAttention = function(test)
+ test:skipTest('cant test this worked')
+end
+
+
+-- love.window.restore
+love.test.window.restore = function(test)
+ -- check minimized to start
+ love.window.minimize()
+ test:waitFrames(10)
+ love.window.restore()
+ test:waitFrames(10)
+ -- check restoring the state of the window
+ test:assertFalse(love.window.isMinimized(), 'check window restored')
+end
+
+
+-- love.window.setDisplaySleepEnabled
+love.test.window.setDisplaySleepEnabled = function(test)
+ -- check disabling sleep
+ love.window.setDisplaySleepEnabled(false)
+ test:assertFalse(love.window.isDisplaySleepEnabled(), 'check sleep disabled')
+ -- check setting it back to enabled
+ love.window.setDisplaySleepEnabled(true)
+ test:assertTrue(love.window.isDisplaySleepEnabled(), 'check sleep enabled')
+end
+
+
+-- love.window.setFullscreen
+love.test.window.setFullscreen = function(test)
+ -- check fullscreen is set
+ love.window.setFullscreen(true)
+ test:assertTrue(love.window.getFullscreen(), 'check fullscreen')
+ -- check setting back to normal
+ love.window.setFullscreen(false)
+ test:assertFalse(love.window.getFullscreen(), 'check not fullscreen')
+end
+
+
+-- love.window.setIcon
+-- @NOTE could check the image data itself?
+love.test.window.setIcon = function(test)
+ -- check setting an icon returns the val
+ local icon = love.image.newImageData('resources/love.png')
+ love.window.setIcon(icon)
+ test:assertNotEquals(nil, love.window.getIcon(), 'check icon not nil')
+end
+
+
+-- love.window.setMode
+-- @NOTE same as getMode could be checking more flag properties
+love.test.window.setMode = function(test)
+ -- set window mode
+ love.window.setMode(512, 512, {
+ fullscreen = false,
+ resizable = false
+ })
+ -- check what we set is returned
+ local width, height, flags = love.window.getMode()
+ test:assertEquals(512, width, 'check window w match')
+ test:assertEquals(512, height, 'check window h match')
+ test:assertFalse(flags["fullscreen"], 'check window not fullscreen')
+ test:assertFalse(flags["resizable"], 'check window not resizeable')
+ love.window.setMode(360, 240, {
+ fullscreen = false,
+ resizable = true
+ })
+end
+
+-- love.window.setPosition
+love.test.window.setPosition = function(test)
+ -- check position is returned
+ love.window.setPosition(100, 100, 1)
+ test:waitFrames(10)
+ local x, y, _ = love.window.getPosition()
+ test:assertEquals(100, x, 'check position x')
+ test:assertEquals(100, y, 'check position y')
+end
+
+
+-- love.window.setTitle
+love.test.window.setTitle = function(test)
+ -- check setting title val is returned
+ love.window.setTitle('love.testing')
+ test:assertEquals('love.testing', love.window.getTitle(), 'check title matches')
+ love.window.setTitle('love.test')
+end
+
+
+-- love.window.setVSync
+love.test.window.setVSync = function(test)
+ love.window.setVSync(0)
+ test:assertNotNil(love.window.getVSync())
+end
+
+
+-- love.window.showMessageBox
+-- @NOTE if running headless would need to skip anyway cos can't press it
+love.test.window.showMessageBox = function(test)
+ test:skipTest('cant test this worked')
+end
+
+
+-- love.window.toPixels
+love.test.window.toPixels = function(test)
+ -- check dpi/pixel ratio is as expected
+ local dpi = love.window.getDPIScale()
+ local pixels = love.window.toPixels(50)
+ test:assertEquals(50*dpi, pixels, 'check dpi ratio')
+end
+
+
+-- love.window.updateMode
+love.test.window.updateMode = function(test)
+ -- set initial mode
+ love.window.setMode(512, 512, {
+ fullscreen = false,
+ resizable = false
+ })
+ -- update mode with some props but not others
+ love.window.updateMode(360, 240, nil)
+ -- check only changed values changed
+ local width, height, flags = love.window.getMode()
+ test:assertEquals(360, width, 'check window w match')
+ test:assertEquals(240, height, 'check window h match')
+ test:assertFalse(flags["fullscreen"], 'check window not fullscreen')
+ test:assertFalse(flags["resizable"], 'check window not resizeable')
+ love.window.setMode(360, 240, { -- reset
+ fullscreen = false,
+ resizable = true
+ })
+end