Browse Source

finished obj tests

- added physics.Contact, physics.Body, and physics.Shape obj tests
- added graphics.Mesh and graphics.ParticleSystem obj tests
- fixed some rgba tolerance needed on a couple methods
- added test conclusion to zip artifact name for quick checking on PRs
- ignored testsuite on compat mode windows builds
ell 1 year ago
parent
commit
825e46621e

+ 22 - 16
.github/workflows/main.yml

@@ -63,6 +63,7 @@ jobs:
         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
@@ -75,7 +76,7 @@ jobs:
     - name: Artifact Test Output (opengl)
       uses: actions/upload-artifact@v3
       with:
-        name: test-output-linux-opengl
+        name: test-output-linux-opengl-${{ steps.report1.outputs.conclusion }}
         path: test-output-linux-opengl.zip
     # linux opengles tests
     - name: Run Test Suite (opengles)
@@ -84,6 +85,7 @@ jobs:
         ./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
@@ -95,7 +97,7 @@ jobs:
     - name: Artifact Test Output (opengles)
       uses: actions/upload-artifact@v3
       with:
-        name: test-output-linux-opengles
+        name: test-output-linux-opengles-${{ steps.report2.outputs.conclusion }}
         path: test-output-linux-opengles.zip
 #    # linux vulkan tests
 #    - name: Run Test Suite (vulkan)
@@ -297,23 +299,25 @@ jobs:
         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'
+      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'
+      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)
-      if: steps.vars.outputs.arch != 'ARM64'
+      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)
@@ -321,23 +325,24 @@ jobs:
         path: megasource/libs/love/testing/output/lovetest_runAllTests.md
         token: ${{ secrets.GITHUB_TOKEN }}
     - name: Zip Test Output (opengl)
-      if: steps.vars.outputs.arch != 'ARM64'
+      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'
+      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 }}${{ steps.vars.outputs.compatname }}-opengl
-        path: test-output-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-opengl.zip
+        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'
+      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)
-      if: steps.vars.outputs.arch != 'ARM64'
+      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)
@@ -345,15 +350,15 @@ jobs:
         path: megasource/libs/love/testing/output/lovetest_runAllTests.md
         token: ${{ secrets.GITHUB_TOKEN }}
     - name: Zip Test Output (opengles)
-      if: steps.vars.outputs.arch != 'ARM64'
+      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'
+      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 }}${{ steps.vars.outputs.compatname }}-opengles
-        path: test-output-windows-${{ steps.vars.outputs.arch }}${{ steps.vars.outputs.compatname }}-opengles.zip
+        name: test-output-windows-${{ steps.vars.outputs.arch }}-opengles-${{ steps.report2.outputs.conclusion }}
+        path: test-output-windows-${{ steps.vars.outputs.arch }}-opengles.zip
 #    # install vulkan
 #    - name: Install Vulkan
 #      if: steps.vars.outputs.arch != 'ARM64'
@@ -427,6 +432,7 @@ jobs:
         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
@@ -439,7 +445,7 @@ jobs:
     - name: Artifact Test Output
       uses: actions/upload-artifact@v3
       with:
-        name: test-output-macos-opengl
+        name: test-output-macos-opengl-${{ steps.report1.outputs.conclusion }}
         path: test-output-macos-opengl.zip
   iOS-Simulator:
     runs-on: macos-latest

+ 6 - 3
testing/main.lua

@@ -47,13 +47,16 @@ love.load = function(args)
       }
       Logo.img = love.graphics.newQuad(0, 0, 64, 64, Logo.texture)
       Font = love.graphics.newFont('resources/font.ttf', 8, 'normal')
-      TextCommand = love.graphics.newTextBatch(Font, 'Loading...')
-      TextRun = love.graphics.newTextBatch(Font, '')
+      local txtobj = love.graphics.newTextBatch or love.graphics.newText
+      TextCommand = txtobj(Font, 'Loading...')
+      TextRun = txtobj(Font, '')
     end
   end
 
   -- mount for output later
-  love.filesystem.mountFullPath(love.filesystem.getSource() .. "/output", "tempoutput", "readwrite")
+  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 = {}

BIN
testing/output/expected/love.test.graphics.ParticleSystem-1.png


+ 38 - 27
testing/readme.md

@@ -1,12 +1,14 @@
 # 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.
+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 in Lua with minimal setup 
+- [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
@@ -19,8 +21,8 @@ Currently written for [Löve 12](https://github.com/love2d/love/tree/12.0-develo
 ---
 
 ## Coverage
-This is the status of all module tests currently.  
-See the **Todo** section for outstanding tests that need writing.
+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  |
@@ -28,10 +30,10 @@ See the **Todo** section for outstanding tests that need writing.
 | 🟢 event          |   4  |   0  |   2  |
 | 🟢 filesystem     |  29  |   0  |   2  |
 | 🟢 font           |   7  |   0  |   0  |
-| 🟡 graphics       | 102  |   2  |   1  |
+| 🟢 graphics       | 104  |   0  |   1  |
 | 🟢 image          |   5  |   0  |   0  |
 | 🟢 math           |  20  |   0  |   0  |
-| 🟡 physics        |  22  |   4  |   0  |
+| 🟢 physics        |  26  |   0  |   0  |
 | 🟢 sound          |   4  |   0  |   0  |
 | 🟢 system         |   6  |   0  |   2  |
 | 🟢 thread         |   5  |   0  |   0  |
@@ -48,9 +50,9 @@ See the **Todo** section for outstanding tests that need writing.
 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 --console`  
-MACOS: `/Applications/love.app/Contents/MacOS/love PATH_TO_TESTING_FOLDER`  
-LINUX: `./love.AppImage PATH_TO_TESTING_FOLDER`
+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:  
@@ -111,32 +113,41 @@ For sanity-checking, if it's currently not covered or it's not possible to test
 
 ---
 
-## Todo 
-Test classes that still need to be written:
-- [ ] graphics.Mesh
-- [ ] graphics.ParticleSystem
-- [ ] physics.Body
-- [ ] physics.Contact
-- [ ] physics.Shape (this will include physics.Fixture properties)
+## 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.
+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  |
-
----
-
-## Future
-- [ ] add BMfont alts for font class tests (Rasterizer + GlyphData)
-- [ ] graphics.isCompressed() should have an example of all compressed files
-- [ ] ability to test loading different combinations of modules
-- [ ] performance tests?
+| 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 |

BIN
testing/resources/pixel.png


+ 284 - 2
testing/tests/graphics.lua

@@ -239,13 +239,291 @@ end
 
 -- Mesh (love.graphics.newMesh)
 love.test.graphics.Mesh = function(test)
-  test:skipTest('test class needs writing')
+  -- 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 properties
+  test:assertEquals('fan', mesh1:getDrawMode(), 'check draw mode')
+  mesh1:setDrawMode('triangles')
+  test:assertEquals('triangles', mesh1:getDrawMode(), 'check draw mode set')
+  local min, max = mesh1:getDrawRange()
+  test:assertEquals(nil, min, 'check draw range not set')
+  mesh1:setDrawRange(1, 10)
+  min, max = mesh1:getDrawRange()
+  test:assertEquals(1, min, 'check draw range set min')
+  test:assertEquals(10, max, 'check draw range set max')
+  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')
+  test:assertEquals(4, mesh1:getVertexCount(), 'check vertex count')
+  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 attributes
+  test:assertEquals(true, mesh1:isAttributeEnabled('VertexPosition'), 'check def attribute VertexPosition')
+  test:assertEquals(true, mesh1:isAttributeEnabled('VertexTexCoord'), 'check def attribute VertexTexCoord')
+  test:assertEquals(true, mesh1:isAttributeEnabled('VertexColor'), 'check def attribute VertexColor')
+  mesh1:setAttributeEnabled('VertexPosition', false)
+  mesh1:setAttributeEnabled('VertexTexCoord', false)
+  mesh1:setAttributeEnabled('VertexColor', false)
+  test:assertEquals(false, mesh1:isAttributeEnabled('VertexPosition'), 'check disable attribute VertexPosition')
+  test:assertEquals(false, mesh1:isAttributeEnabled('VertexTexCoord'), 'check disable attribute VertexTexCoord')
+  test:assertEquals(false, mesh1:isAttributeEnabled('VertexColor'), 'check disable attribute VertexColor')
+  -- check vertex
+  local x, y, u, v, r, g, b, a = mesh1:getVertex(1)
+  test:assertEquals(0, x, 'check vertex props x')
+  test:assertEquals(0, y, 'check vertex props y')
+  test:assertEquals(0, u, 'check vertex props u')
+  test:assertEquals(0, v, 'check vertex props v')
+  test:assertEquals(1, r, 'check vertex props r')
+  test:assertEquals(0, g, 'check vertex props g')
+  test:assertEquals(0, b, 'check vertex props b')
+  test:assertEquals(1, a, 'check vertex props a')
+  mesh1:setVertex(2, image:getWidth(), 0, 1, 0, 0, 1, 1, 1)
+  x, y, u, v, r, g, b, a = mesh1:getVertex(2)
+  test:assertEquals(image:getWidth(), x, 'check changed vertex props x')
+  test:assertEquals(0, y, 'check changed vertex props y')
+  test:assertEquals(1, u, 'check changed vertex props u')
+  test:assertEquals(0, v, 'check changed vertex props v')
+  test:assertEquals(0, r, 'check changed vertex props r')
+  test:assertEquals(1, g, 'check changed vertex props g')
+  test:assertEquals(1, b, 'check changed vertex props b')
+  test:assertEquals(1, a, 'check changed vertex props a')
+  r, g, b, a  = mesh1:getVertexAttribute(3, 3)
+  test:assertEquals(1, b, 'check specific vertex color')
+  mesh1:setVertexAttribute(4, 3, 1, 0, 1)
+  r, g, b, a  = mesh1:getVertexAttribute(4, 3)
+  test:assertEquals(0, g, 'check changed vertex color')
+  -- change vertices
+  mesh1:setVertices(vertices)
+  r, g, b, a  = mesh1:getVertexAttribute(4, 3)
+  x, y, u, v, r, g, b, a = mesh1:getVertex(2)
+  test:assertEquals(1, g, 'check reset vertex color 1')
+  test:assertEquals(0, b, 'check reset vertex color 2')
+  local vmap = mesh1:getVertexMap()
+  test:assertEquals(nil, vmap, 'check no map by def')
+  mesh1:setVertexMap({4, 1, 2, 3})
+  vmap = mesh1:getVertexMap()
+  test:assertEquals(4, #vmap, 'check set map len')
+  test:assertEquals(2, vmap[3], 'check set map val')
+  -- 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')
+  mesh1:attachAttribute('CustomValue1', mesh2)
+  test:assertEquals(true, 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:assertEquals(true, mesh1:isAttributeEnabled('VertexPosition'), 'check cant detach def attribute')
 end
 
 
 -- ParticleSystem (love.graphics.newParticleSystem)
 love.test.graphics.ParticleSystem = function(test)
-  test:skipTest('test class needs writing')
+  -- 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 properties
+  psystem:start()
+  psystem:update(1)
+  test:assertEquals(true, psystem:isActive(), 'check active')
+  test:assertEquals(false, psystem:isPaused(), 'checked not paused by def')
+  test:assertEquals(false, psystem:hasRelativeRotation(), 'check rel rot def')
+  psystem:pause()
+  test:assertEquals(true, psystem:isPaused(), 'check now paused')
+  test:assertEquals(false, psystem:isStopped(), 'check not stopped by def')
+  psystem:stop()
+  test:assertEquals(true, psystem:isStopped(), 'check now stopped')
+  psystem:start()
+  psystem:reset()
+  -- need to set lifetime here or count will be 0
+  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 particle get/sets 
+  local colors = {psystem:getColors()}
+  test:assertEquals(1, #colors, 'check 1 color by def')
+  psystem:setColors(1, 1, 1, 1, 1, 0, 0, 1)
+  colors = {psystem:getColors()}
+  test:assertEquals(2, #colors, 'check set colors')
+  test:assertEquals(1, colors[2][1], 'check set color')
+  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')
+  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:assertEquals(false, rel, 'check emission area rel')
+  test:assertEquals(0, psystem:getEmissionRate(), 'check def emission rate')
+  psystem:setEmissionRate(1)
+  test:assertEquals(1, psystem:getEmissionRate(), 'check changed emission rate')
+  test:assertEquals(-1, psystem:getEmitterLifetime(), 'check def emitter life')
+  psystem:setEmitterLifetime(10)
+  test:assertEquals(10, psystem:getEmitterLifetime(), 'check changed emitter life')
+  test:assertEquals('top', psystem:getInsertMode(), 'check def insert mode')
+  psystem:setInsertMode('bottom')
+  psystem:setInsertMode('random')
+  test:assertEquals('random', psystem:getInsertMode(), 'check change insert mode')
+  local xmin, ymin, xmax, ymax = psystem:getLinearAcceleration()
+  test:assertEquals(0, xmin, 'check def lin acceleration xmin')
+  test:assertEquals(0, ymin, 'check def lin acceleration ymin')
+  test:assertEquals(0, xmax, 'check def lin acceleration xmax')
+  test:assertEquals(0, ymax, 'check def lin acceleration ymax')
+  psystem:setLinearAcceleration(1, 2, 3, 4)
+  xmin, ymin, xmax, ymax = psystem:getLinearAcceleration()
+  test:assertEquals(1, xmin, 'check change lin acceleration xmin')
+  test:assertEquals(2, ymin, 'check change lin acceleration ymin')
+  test:assertEquals(3, xmax, 'check change lin acceleration xmax')
+  test:assertEquals(4, ymax, 'check change lin acceleration ymax')
+  min, max = psystem:getLinearDamping()
+  test:assertEquals(0, min, 'check def lin damping min')
+  test:assertEquals(0, max, 'check def lin damping max')
+  psystem:setLinearDamping(1, 2)
+  min, max = psystem:getLinearDamping()
+  test:assertEquals(1, min, 'check change lin damping min')
+  test:assertEquals(2, max, 'check change lin damping max')
+  local ox, oy = psystem:getOffset()
+  -- 0.5 cos middle of pixel image which is 1x1
+  test:assertEquals(0.5, ox, 'check def offset x')
+  test:assertEquals(0.5, oy, 'check def offset y')
+  psystem:setOffset(0, 10)
+  ox, oy = psystem:getOffset()
+  test:assertEquals(0, ox, 'check change offset x')
+  test:assertEquals(10, oy, 'check change offset y')
+  min, max = psystem:getParticleLifetime()
+  test:assertEquals(1, min, 'check p lifetime min')
+  test:assertEquals(2, max, 'check p lifetime max')
+  local x, y = psystem:getPosition()
+  test:assertEquals(0, x, 'check emitter x')
+  test:assertEquals(0, y, 'check emitter y')
+  psystem:setPosition(10, 12)
+  x, y = psystem:getPosition()
+  test:assertEquals(10, x, 'check set emitter x')
+  test:assertEquals(12, y, 'check set emitter y')
+  test:assertEquals(0, #psystem:getQuads(), 'check def quads')
+  psystem:setQuads({quad1})
+  psystem:setQuads(quad1, quad2)
+  test:assertEquals(2, #psystem:getQuads(), 'check set quads')
+  min, max = psystem:getRadialAcceleration()
+  test:assertEquals(0, min, 'check def rad accel min')
+  test:assertEquals(0, max, 'check def rad accel max')
+  psystem:setRadialAcceleration(1, 2)
+  min, max = psystem:getRadialAcceleration()
+  test:assertEquals(1, min, 'check change rad accel min')
+  test:assertEquals(2, max, 'check change rad accel max')
+  min, max = psystem:getRotation()
+  test:assertEquals(0, min, 'check def rot min')
+  test:assertEquals(0, max, 'check def rot max')
+  psystem:setRotation(90 * (math.pi/180), 180 * (math.pi/180))
+  min, max = psystem:getRotation()
+  test:assertEquals(math.floor(math.pi/2*100), math.floor(min*100), 'check set rot min')
+  test:assertEquals(math.floor(math.pi*100), math.floor(max*100), 'check set rot max')
+  test:assertEquals(0, psystem:getSizeVariation(), 'check def variation')
+  psystem:setSizeVariation(1)
+  test:assertEquals(1, psystem:getSizeVariation(), 'check change variation')
+  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')
+  min, max = psystem:getSpeed()
+  test:assertEquals(0, min, 'check def speed min')
+  test:assertEquals(0, max, 'check def speed max')
+  psystem:setSpeed(1, 10)
+  min, max = psystem:getSpeed()
+  test:assertEquals(1, min, 'check change speed min')
+  test:assertEquals(10, max, 'check change speed max')
+  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)
+  min, max = psystem:getSpin()
+  test:assertEquals(1, min, 'check change spin min')
+  test:assertEquals(2, max, 'check change spin max')
+  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')
+  min, max = psystem:getTangentialAcceleration()
+  test:assertEquals(0, min, 'check def tan accel min')
+  test:assertEquals(0, max, 'check def tan accel max')
+  psystem:setTangentialAcceleration(1, 2)
+  min, max = psystem:getTangentialAcceleration()
+  test:assertEquals(1, min, 'check change tan accel min')
+  test:assertEquals(2, max, 'check change tan accel max')
+  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
 
 
@@ -744,6 +1022,8 @@ love.test.graphics.drawInstanced = function(test)
     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
 
@@ -1518,6 +1798,8 @@ love.test.graphics.setBlendMode = function(test)
     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
 

+ 318 - 4
testing/tests/physics.lua

@@ -10,13 +10,206 @@
 
 -- Body (love.physics.newBody)
 love.test.physics.Body = function(test)
-  test:skipTest('test class needs writing')
+  -- 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 state properties
+  test:assertEquals(true, body1:isActive(), 'check active by def')
+  test:assertEquals(false, body1:isBullet(), 'check not bullet by def')
+  body1:setBullet(true)
+  test:assertEquals(true, body1:isBullet(), 'check set bullet')
+  test:assertEquals(false, body1:isFixedRotation(), 'check fix rot def')
+  body1:setFixedRotation(true)
+  test:assertEquals(true, body1:isFixedRotation(), 'check set fix rot')
+  test:assertEquals(true, body1:isSleepingAllowed(), 'check sleep def')
+  body1:setSleepingAllowed(false)
+  test:assertEquals(false, body1:isSleepingAllowed(), 'check set sleep')
+  body1:setSleepingAllowed(true)
+  world:update(1)
+  test:assertEquals(false, body1:isAwake(), 'check fell asleep')
+  body1:setSleepingAllowed(false)
+  body1:setType('dynamic')
+  test:assertEquals(true, body1:isAwake(), 'check waking up')
+  test:assertEquals(false, body1:isTouching(body2))
+  body2:setPosition(5, 5)
+  world:update(1)
+  test:assertEquals(true, body1:isTouching(body2))
+  -- check body properties
+  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')
+  local x, y = body1:getLocalCenter()
+  test:assertEquals(5, math.floor(x), 'check local center x')
+  test:assertEquals(5, math.floor(y), 'check local center y')
+  local lx, ly = body1:getLocalPoint(10, 10)
+  test:assertEquals(10, math.floor(lx), 'check local point x')
+  test:assertEquals(9, math.floor(ly), 'check local point y')
+  local lx1, ly1, lx2, ly2 = body1:getLocalPoints(0, 5, 5, 10)
+  test:assertEquals(0, math.floor(lx1), 'check local points x 1')
+  test:assertEquals(4, math.floor(ly1), 'check local points y 1')
+  test:assertEquals(5, math.floor(lx2), 'check local points x 2')
+  test:assertEquals(9, math.floor(ly2), 'check local points y 2')
+  local wx, wy = body1:getWorldPoint(10.4, 9)
+  test:assertEquals(10, math.floor(wx), 'check world point x')
+  test:assertEquals(10, math.floor(wy), 'check world point y')
+  local wx1, wy1, wx2, wy2 = body1:getWorldPoints(0.4, 4, 5.4, 9)
+  test:assertEquals(0, math.floor(wx1), 'check world points x 1')
+  test:assertEquals(5, math.floor(wy1), 'check world points y 1')
+  test:assertEquals(5, math.floor(wx2), 'check world points x 2')
+  test:assertEquals(10, math.floor(wy2), 'check world points y 2')
+  test:assertEquals(0, body1:getAngularDamping(), 'check angular damping')
+  test:assertEquals(0, body1:getAngularVelocity(), 'check angular velocity')
+  test:assertObject(body1:getWorld())
+  test:assertEquals(2, body1:getWorld():getBodyCount(), 'check world count')
+  local cx, cy = body1:getWorldCenter()
+  test:assertEquals(46, math.floor(cx*10), 'check world center x')
+  test:assertEquals(60, math.floor(cy*10), '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')
+  test:assertEquals(555, math.floor(body1:getInertia()*100), 'check inertia')
+  -- check get/set properties
+  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')
+  test:assertEquals(1, body1:getGravityScale(), 'check def grav')
+  body1:setGravityScale(2)
+  test:assertEquals(2, body1:getGravityScale(), 'check change grav')
+  test:assertEquals(0, body1:getLinearDamping(), 'check def lin damping')
+  body1:setLinearDamping(0.1)
+  test:assertEquals(1, math.floor(body1:getLinearDamping()*10), 'check change lin damping')
+  x, y = body1:getLinearVelocity()
+  test:assertEquals(1, x, 'check def lin velocity x')
+  test:assertEquals(1, y, 'check def lin velocity y')
+  body1:setLinearVelocity(4, 5)
+  x, y = body1:getLinearVelocity()
+  test:assertEquals(4, x, 'check change lin velocity x')
+  test:assertEquals(5, y, 'check change lin velocity y')
+  test:assertEquals(1, math.floor(body1:getMass()*10), 'check def mass')
+  body1:setMass(10)
+  test:assertEquals(10, body1:getMass(), 'check change mass')
+  body1:setMassData(3, 5, 10, 1)
+  local x, y, mass, inertia = body1:getMassData()
+  test:assertEquals(3, x, 'check mass data change x')
+  test:assertEquals(5, y, 'check mass data change y')
+  test:assertEquals(10, mass, 'check mass data change mass')
+  test:assertEquals(340, math.floor(inertia), 'check mass data change inertia')
+  body1:resetMassData()
+  x, y, mass, inertia = body1:getMassData()
+  test:assertEquals(5, math.floor(x), 'check mass data reset x')
+  test:assertEquals(5, math.floor(y), 'check mass data reset y')
+  test:assertEquals(1, math.floor(mass*10), 'check mass data reset mass')
+  test:assertEquals(5, math.floor(inertia), 'check mass data reset inertia')
+  x, y = body1:getPosition()
+  test:assertEquals(-1, math.floor(x), 'check position x')
+  test:assertEquals(0, math.floor(y), 'check position y')
+  body1:setPosition(10, 4)
+  x, y = body1:getPosition()
+  test:assertEquals(10, math.floor(x), 'check set position x')
+  test:assertEquals(4, math.floor(y), 'check set position y')
+  test:assertEquals('dynamic', body1:getType(), 'check type match')
+  body1:setType('kinematic')
+  body1:setType('static')
+  test:assertEquals('static', body1:getType(), 'check type change')
+  test:assertEquals(nil, body1:getUserData(), 'check user data')
+  body1:setUserData({ love = 'cool' })
+  test:assertEquals('cool', body1:getUserData().love, 'check set user data')
+  test:assertEquals(10, body1:getX(), 'check get x')
+  test:assertEquals(4, 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 some force
+  local vel = body2:getAngularVelocity()
+  test:assertEquals(0, math.floor(vel), 'check velocity before')
+  body2:applyAngularImpulse(10)
+  vel = body2:getAngularVelocity()
+  test:assertEquals(54, math.floor(vel*10), 'check velocity after 1')
+  local ang = body2:getAngle()
+  test:assertEquals(149, math.floor(ang*1000), 'check initial angle')
+  body2:applyForce(2, 3)
+  world:update(2)
+  vel = body2:getAngularVelocity()
+  ang = body2:getAngle()
+  test:assertEquals(-84, math.floor(ang*1000), 'check angle after')
+  test:assertEquals(124, math.floor(vel*100), 'check velocity after 2')
+  body2:applyLinearImpulse(-4, -59)
+  world:update(1)
+  ang = body2:getAngle()
+  vel = body2:getAngularVelocity()
+  test:assertEquals(-1572, math.floor(ang*1000), 'check angle after 2')
+  test:assertEquals(9, math.floor(vel*100000000), 'check velocity after 3')
+  body2:applyTorque(4)
+  world:update(2)
+  ang = body2:getAngle()
+  vel = body2:getAngularVelocity()
+  test:assertEquals(-912, math.floor(ang*1000), 'check angle after 3')
+  test:assertEquals(321, math.floor(vel*1000), 'check velocity after 4')
+  test:assertEquals(false, body1:isDestroyed(), 'check not destroyed')
+  body1:destroy()
+  test:assertEquals(true, body1:isDestroyed(), 'check destroyed')
 end
 
 
 -- Contact (love.physics.World:getContacts)
 love.test.physics.Contact = function(test)
-  test:skipTest('test class needs writing')
+  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')
+  local collided = false
+  local pass = 1
+  world:setCallbacks(
+    function(shape_a, shape_b, contact)
+      collided = true
+      test:assertObject(contact)
+      local indexA, indexB = contact:getChildren()
+      test:assertEquals(1, indexA, 'check child indice a')
+      test:assertEquals(1, indexB, 'check child indice b')
+      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')
+      local nx, ny = contact:getNormal()
+      test:assertEquals(1, nx, 'check normal x')
+      test:assertEquals(0, ny, 'check normal y')
+      local px1, py1, px2, py2 = contact:getPositions()
+      test:assertEquals(5, math.floor(px1), 'check collide x 1')
+      test:assertEquals(5, math.floor(py1), 'check collide y 1')
+      test:assertEquals(5, math.floor(px2), 'check collide x 2')
+      test:assertEquals(5, math.floor(py2), 'check collide y 2')
+      test:assertEquals(true, contact:isTouching(), 'check touching')
+      test:assertEquals(pass == 1, contact:isEnabled(), 'check enabled for pass ' .. tostring(pass))
+      test:assertEquals(2, math.floor(contact:getFriction()*10), 'check def friction')
+      contact:setFriction(0.1)
+      test:assertEquals(1, math.floor(contact:getFriction()*10), 'check set friction')
+      contact:resetFriction()
+      test:assertEquals(2, math.floor(contact:getFriction()*10), 'check reset friction')
+      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
+  )
+  world:update(1)
+  test:assertEquals(true, collided, 'check bodies collided')
+  -- update again for enabled check
+  world:update(1)
+  test:assertEquals(2, pass, 'check ran twice')
 end
 
 
@@ -52,10 +245,131 @@ love.test.physics.Joint = function(test)
 end
 
 
+--love.test.physics.Test1 = function(test)
+--  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)
+--  local tlx, tly, brx, bry = shape1:getBoundingBox(1)
+--  print('position:', tlx, tly, brx, bry) -- (-0.3, -0.3, 10.3, 10.3)
+--  test:assertEquals(true, shape1:testPoint(5, 5), 'check point 1') -- returns false
+--end
+
+
 -- Shape (love.physics.newCircleShape)
--- @NOTE includes Fixture methods too now so enjoy
+-- @NOTE in 12.0 fixtures have been merged into shapes
 love.test.physics.Shape = function(test)
-  test:skipTest('test class needs writing')
+  -- 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 base properties
+  test:assertEquals(1, shape1:getChildCount(), 'check child count')
+  test:assertEquals(0, math.floor(shape1:getRadius()), 'check radius')
+  test:assertEquals('polygon', shape1:getType(), 'check rectangle type')
+  test:assertEquals(0, shape1:getBody():getX(), 'check body link')
+  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')
+  test:assertEquals(false, shape1:isSensor(), 'check sensor def')
+  shape1:setSensor(true)
+  test:assertEquals(true, shape1:isSensor(), 'check set sensor')
+  shape1:setSensor(false)
+  test:assertEquals(false, shape1:isDestroyed(), 'check not destroyed')
+  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:assertEquals(-3, math.floor(topLeftY*10), 'check bbox tl 2')
+  test:assertEquals(bottomRightX, bottomRightY, 'check bbox br 1')
+  test:assertEquals(10, math.floor(bottomRightX), 'check bbox br 2')
+  -- check physics props
+  test:assertEquals(1, shape1:getDensity(), 'check def density')
+  shape1:setDensity(5)
+  test:assertEquals(5, shape1:getDensity(), 'check set density')
+  local x, y, mass, inertia = shape1:getMassData()
+  test:assertEquals(5, math.floor(x), 'check shape mass pos x')
+  test:assertEquals(5, math.floor(y), 'check shape mass pos y')
+  test:assertEquals(5, math.floor(mass*10), 'check mass at 1 density')
+  test:assertEquals(0, math.floor(inertia*10), 'check intertia at 1 density')
+  x, y, mass, inertia = shape1:computeMass(1000)
+  test:assertEquals(111, math.floor(mass), 'check mass at 1000 density')
+  test:assertEquals(7407, math.floor(inertia), 'check intertia at 1000 density')
+  test:assertEquals(2, math.floor(shape1:getFriction()*10), 'check def friction')
+  shape1:setFriction(1)
+  test:assertEquals(1, shape1:getFriction(), 'check set friction')
+  test:assertEquals(0, shape1:getRestitution(), 'check def restitution')
+  shape1:setRestitution(0.5)
+  test:assertEquals(5, math.floor(shape1:getRestitution()*10), 'check set restitution')
+  -- check points
+  local shape2 = love.physics.newRectangleShape(body1, 5, 5, 10, 10)
+  tlx, tly, brx, bry = shape2:getBoundingBox(1)
+  test:assertEquals(true, shape2:testPoint(5, 5), 'check point 5,5')
+  test:assertEquals(true, shape2:testPoint(15, 15, 10, 10, 0), 'check point 15,15 after translate 10,10')
+  test:assertEquals(true, shape2:testPoint(15, 15, 10, 10, 90), 'check point 15,15 after translate 10,10,90')
+  test:assertEquals(false, shape2:testPoint(5, 5, 10, 10, 90), 'check point 5,5 after translate 10,10,90')
+  test:assertEquals(false, shape2:testPoint(15, 15), 'check point 15,15')
+  local xn, yn, fraction = shape2:rayCast(-20, -20, 20, 20, 100, 0, 0, 0, 1)
+  test:assertNotEquals(nil, xn, 'check ray 1 x')
+  test:assertNotEquals(nil, xn, 'check ray 1 y')
+  xn, yn, fraction = shape2:rayCast(10, 10, -150, -150, 100, 0, 0, 0, 1)
+  test:assertEquals(nil, xn, 'check ray 2 x')
+  test:assertEquals(nil, xn, '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')
+  -- run some collision checks using filters
+  shape1:destroy()
+  test:assertEquals(true, shape1:isDestroyed(), 'check destroyed')
+  shape2:destroy()
+  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 group will always collide if the group is positive or never collide if it's negative
+  shape3:setGroupIndex(1)
+  shape4:setGroupIndex(1)
+  world:update(1)
+  test:assertEquals(1, collisions, 'check positive group 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')
+  -- mask sets which categories this fixture should NOT collide with.
+  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')
+  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