Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit testing framework and example #192

Merged
merged 2 commits into from
Jul 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ before_install:
- pip install --upgrade pip
install:
- pip install -r requirements.txt
- pip install flake8
- pip install flake8 pytest pytest-mock
before_script:
# fail the build if there are Python syntax errors or undefined names
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
Expand Down Expand Up @@ -56,6 +56,6 @@ script:
- sudo chown -R $USER ~/.npm
- "cd generator/javascript && npm test"

# Test quaternions
# Run tests
- cd $TRAVIS_BUILD_DIR
- tools/quaterniontest.py
- python -m pytest
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Filter pytest to only running files with the following pattern
[pytest]
python_files = test_*.py
59 changes: 10 additions & 49 deletions rotmat.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ def __sub__(self, m):
def __rsub__(self, m):
return Matrix3(m.a - self.a, m.b - self.b, m.c - self.c)

def __eq__(self, m):
return self.a == m.a and self.b == m.b and self.c == m.c

def __ne__(self, m):
return not self == m

def __mul__(self, other):
if isinstance(other, Vector3):
v = other
Expand Down Expand Up @@ -263,7 +269,9 @@ def __copy__(self):
copy = __copy__

def rotate(self, g):
'''rotate the matrix by a given amount on 3 axes'''
'''rotate the matrix by a given amount on 3 axes,
where g is a vector of delta angles. Used
with DCM updates in mavextra.py'''
temp_matrix = Matrix3()
a = self.a
b = self.b
Expand Down Expand Up @@ -326,7 +334,7 @@ def from_two_vectors(self, vec1, vec2):
return self.from_axis_angle(cross, angle)

def close(self, m, tol=1e-7):
return self.a.close(m.a) and self.b.close(m.b) and self.c.close(m.c)
return self.a.close(m.a, tol) and self.b.close(m.b, tol) and self.c.close(m.c, tol)

class Plane(object):
'''a plane in 3 space, defined by a point and a vector normal'''
Expand Down Expand Up @@ -359,50 +367,3 @@ def plane_intersection(self, plane, forward_only=False):
return None
return (self.vector * d) + self.point



def test_euler():
'''check that from_euler() and to_euler() are consistent'''
m = Matrix3()
for r in range(-179, 179, 3):
for p in range(-89, 89, 3):
for y in range(-179, 179, 3):
m.from_euler(radians(r), radians(p), radians(y))
(r2, p2, y2) = m.to_euler()
v1 = Vector3(r,p,y)
v2 = Vector3(degrees(r2),degrees(p2),degrees(y2))
diff = v1 - v2
if diff.length() > 1.0e-12:
print('EULER ERROR:', v1, v2, diff.length())


def test_two_vectors():
'''test the from_two_vectors() method'''
import random
for i in range(1000):
v1 = Vector3(1, 0.2, -3)
v2 = Vector3(random.uniform(-5,5), random.uniform(-5,5), random.uniform(-5,5))
m = Matrix3()
m.from_two_vectors(v1, v2)
v3 = m * v1
diff = v3.normalized() - v2.normalized()
(r, p, y) = m.to_euler()
if diff.length() > 0.001:
print('err=%f' % diff.length())
print("r/p/y = %.1f %.1f %.1f" % (
degrees(r), degrees(p), degrees(y)))
print(v1.normalized(), v2.normalized(), v3.normalized())

def test_plane():
'''testing line/plane intersection'''
print("testing plane/line maths")
plane = Plane(Vector3(0,0,0), Vector3(0,0,1))
line = Line(Vector3(0,0,100), Vector3(10, 10, -90))
p = line.plane_intersection(plane)
print(p)

if __name__ == "__main__":
import doctest
doctest.testmod()
test_euler()
test_two_vectors()
32 changes: 16 additions & 16 deletions tools/quaterniontest.py → tests/test_quaternion.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,26 +154,26 @@ def test_conversion(self):
q0 = q
e = QuaternionBase(q.q).euler
q1 = QuaternionBase(e)
self.assertTrue(q0.close(q1))
assert q0.close(q1)

# quaternion -> dcm -> quaternion
q0 = q
dcm = QuaternionBase(q.q).dcm
q1 = QuaternionBase(dcm)
self.assertTrue(q0.close(q1))
assert q0.close(q1)

def test_inversed(self):
"""Test inverse"""
for q in self.quaternions:
q_inv = q.inversed
q_inv_inv = q_inv.inversed
self.assertTrue(q.close(q_inv_inv))
assert q.close(q_inv_inv)

def test_mul(self):
"""Test multiplication"""
for q in self.quaternions:
for p in self.quaternions:
self.assertTrue(q.close(p * p.inversed * q))
assert q.close(p * p.inversed * q)
r = p * q
r_dcm = np.dot(p.dcm, q.dcm)
np.testing.assert_almost_equal(r_dcm, r.dcm)
Expand All @@ -184,7 +184,7 @@ def test_div(self):
for p in self.quaternions:
mul = q * p.inversed
div = q / p
self.assertTrue(mul.close(div))
assert mul.close(div)

def test_transform(self):
"""Test transform"""
Expand Down Expand Up @@ -251,7 +251,7 @@ def _helper_test_constructor(self, q, euler, dcm):
q_test = Quaternion(quaternion_instance)
np.testing.assert_almost_equal(q_test.q, q)
q_test = Quaternion(quaternion_instance)
self.assertTrue(q_test.dcm.close(dcm))
assert q_test.dcm.close(dcm)
q_test = Quaternion(quaternion_instance)
q_euler = Quaternion(q_test.euler)
assert(np.allclose(q_test.euler, euler) or
Expand All @@ -262,7 +262,7 @@ def _helper_test_constructor(self, q, euler, dcm):
q_test = Quaternion(quaternion_instance)
np.testing.assert_almost_equal(q_test.q, q)
q_test = Quaternion(quaternion_instance)
self.assertTrue(q_test.dcm.close(dcm))
assert q_test.dcm.close(dcm)
q_test = Quaternion(quaternion_instance)
q_euler = Quaternion(q_test.euler)
assert(np.allclose(q_test.euler, euler) or
Expand All @@ -272,7 +272,7 @@ def _helper_test_constructor(self, q, euler, dcm):
q_test = Quaternion(q)
np.testing.assert_almost_equal(q_test.q, q)
q_test = Quaternion(q)
self.assertTrue(q_test.dcm.close(dcm))
assert q_test.dcm.close(dcm)
q_test = Quaternion(q)
q_euler = Quaternion(q_test.euler)
assert(np.allclose(q_test.euler, euler) or
Expand All @@ -282,7 +282,7 @@ def _helper_test_constructor(self, q, euler, dcm):
q_test = Quaternion(euler)
np.testing.assert_almost_equal(q_test.q, q)
q_test = Quaternion(euler)
self.assertTrue(q_test.dcm.close(dcm))
assert q_test.dcm.close(dcm)
q_test = Quaternion(euler)
q_euler = Quaternion(q_test.euler)
assert(np.allclose(q_test.euler, euler) or
Expand All @@ -292,7 +292,7 @@ def _helper_test_constructor(self, q, euler, dcm):
q_test = Quaternion(dcm)
np.testing.assert_almost_equal(q_test.q, q)
q_test = Quaternion(dcm)
self.assertTrue(q_test.dcm.close(dcm))
assert q_test.dcm.close(dcm)
q_test = Quaternion(dcm)
q_euler = Quaternion(q_test.euler)
assert(np.allclose(q_test.euler, euler) or
Expand All @@ -307,13 +307,13 @@ def test_conversion(self):
q0 = q
e = Quaternion(q.q).euler
q1 = Quaternion(e)
self.assertTrue(q0.close(q1))
assert q0.close(q1)

# quaternion -> dcm (Matrix3) -> quaternion
q0 = q
dcm = Quaternion(q.q).dcm
q1 = Quaternion(dcm)
self.assertTrue(q0.close(q1))
assert q0.close(q1)

def test_transform(self):
"""Test transform"""
Expand All @@ -322,18 +322,18 @@ def test_transform(self):
v = Vector3(1, 2, 3)
v1 = q.transform(v)
v1_dcm = q.dcm * v
self.assertTrue(v1.close(v1_dcm))
assert v1.close(v1_dcm)
v2 = q_inv.transform(v1)
self.assertTrue(v.close(v2))
assert v.close(v2)

def test_mul(self):
"""Test multiplication"""
for q in self.quaternions:
for p in self.quaternions:
self.assertTrue(q.close(p * p.inversed * q))
assert q.close(p * p.inversed * q)
r = p * q
r_dcm = p.dcm * q.dcm
self.assertTrue(r_dcm.close(r.dcm))
assert r_dcm.close(r.dcm)


if __name__ == '__main__':
Expand Down
161 changes: 161 additions & 0 deletions tests/test_rotmat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env python


"""
Unit tests for the rotmat library
"""

from __future__ import absolute_import, division, print_function
from math import radians, degrees
import unittest
import random
import numpy as np

from pymavlink.rotmat import Vector3, Matrix3, Plane, Line

class VectorTest(unittest.TestCase):

"""
Class to test Vector3
"""

def __init__(self, *args, **kwargs):
"""Constructor, set up some data that is reused in many tests"""
super(VectorTest, self).__init__(*args, **kwargs)


def test_constructor(self):
"""Test the constructor functionality"""
v1 = Vector3(1, 0.2, -3)
v2 = Vector3([1, 0.2, -3])
v3 = Vector3([1, 0.3, -3])

assert v1 == v2
assert v1 != v3
assert str(v1) == "Vector3(1.00, 0.20, -3.00)"


def test_maths(self):
"""Test simple maths"""
v1 = Vector3(1, 2, -3)
v2 = Vector3(1, 3, 3)

assert v1 + v2 == Vector3(2, 5, 0)
assert v1 - v2 == Vector3(0, -1, -6)
assert (v1 * 3) == Vector3(3, 6, -9)
assert v1 * v2 == -2

assert v1 % v2 == Vector3(15.00, -6.00, 1.00)
np.testing.assert_almost_equal(v2.length(), 4.358898943540674)
assert v2.normalized().close(Vector3(0.23, 0.69, 0.69), tol=1e-2)
np.testing.assert_almost_equal(v1.angle(v2), 1.693733631245806)


class MatrixTest(unittest.TestCase):

"""
Class to test Matrix3
"""

def __init__(self, *args, **kwargs):
"""Constructor, set up some data that is reused in many tests"""
super(MatrixTest, self).__init__(*args, **kwargs)

def test_constructor(self):
"""Test the constructor functionality"""
m1 = Matrix3(Vector3(1, 0, 0), Vector3(1, 5, 0), Vector3(1, 0, -7))
m2 = Matrix3()

assert str(m1) == 'Matrix3((1.00, 0.00, 0.00), (1.00, 5.00, 0.00), (1.00, 0.00, -7.00))'
assert str(m2) == 'Matrix3((1.00, 0.00, 0.00), (0.00, 1.00, 0.00), (0.00, 0.00, 1.00))'

def test_maths(self):
m1 = Matrix3(Vector3(1, 0, 0), Vector3(1, 5, 0), Vector3(1, 0, -7))
m2 = Matrix3()

assert m1 + m2 == Matrix3(Vector3(2, 0, 0), Vector3(1, 6, 0), Vector3(1, 0, -6))
assert m1 - m2 == Matrix3(Vector3(0, 0, 0), Vector3(1, 4, 0), Vector3(1, 0, -8))
assert m1 * 3 == Matrix3(Vector3(3, 0, 0), Vector3(3, 15, 0), Vector3(3, 0, -21))
assert m1 * m1 == Matrix3(Vector3(1, 0, 0), Vector3(6, 25, 0), Vector3(-6, 0, 49))
assert m1.transposed() == Matrix3(Vector3(1, 1, 1), Vector3(0, 5, 0), Vector3(0, 0, -7))

def test_euler(self):
'''check that from_euler() and to_euler() are consistent'''
m = Matrix3()
for r in range(-179, 179, 10):
for p in range(-89, 89, 10):
for y in range(-179, 179, 10):
m.from_euler(radians(r), radians(p), radians(y))
(r2, p2, y2) = m.to_euler()
v1 = Vector3(r, p, y)
v2 = Vector3(degrees(r2), degrees(p2), degrees(y2))
diff = v1 - v2
assert diff.length() < 1.0e-12

def test_euler312(self):
'''check that from_euler312() and to_euler312() are consistent'''
m = Matrix3()
for r in range(-89, 89, 10):
for p in range(-179, 179, 10):
for y in range(-179, 179, 10):
m.from_euler312(radians(r), radians(p), radians(y))
(r2, p2, y2) = m.to_euler312()
v1 = Vector3(r, p, y)
v2 = Vector3(degrees(r2), degrees(p2), degrees(y2))
diff = v1 - v2
assert diff.length() < 1.0e-12

def test_matrixops(self):
m1 = Matrix3(Vector3(1, 0, 0), Vector3(1, 5, 0), Vector3(1, 0, -7))

m1.normalize()
print(m1)
assert m1.close(Matrix3(Vector3(0.2, -0.98, 0), Vector3(0.1, 1, 0), Vector3(0, 0, 1)), tol=1e-2)
np.testing.assert_almost_equal(m1.trace(), 2.19115332535)

m1.rotate(Vector3(0.2,-0.98,0))
assert m1.close(Matrix3(Vector3(0.2,-0.98,0), Vector3(0.1,1,-0.3), Vector3(0.98,0.2,1)), tol=1e-2)

def test_axisangle(self):
axis = Vector3(0, 1, 0)
angle = radians(45)

m1 = Matrix3()
m1.from_axis_angle(axis, angle)
#print(m1)
assert m1.close(Matrix3(Vector3(0.71, 0.00, 0.71),
Vector3(0.00, 1.00, 0.00),
Vector3(-0.71, 0.00, 0.71)), tol=1e-2)

def test_two_vectors(self):
'''test the from_two_vectors() method'''
for i in range(100):
v1 = Vector3(1, 0.2, -3)
v2 = Vector3(random.uniform(-5, 5), random.uniform(-5, 5), random.uniform(-5, 5))
m = Matrix3()
m.from_two_vectors(v1, v2)
v3 = m * v1
diff = v3.normalized() - v2.normalized()
(r, p, y) = m.to_euler()
assert diff.length() < 0.001


class LinePlaneTest(unittest.TestCase):

"""
Class to test Line and Plane classes
"""

def __init__(self, *args, **kwargs):
"""Constructor, set up some data that is reused in many tests"""
super(LinePlaneTest, self).__init__(*args, **kwargs)

def test_plane(self):
'''testing line/plane intersection'''
plane = Plane(Vector3(0, 0, 0), Vector3(0, 0, 1))
line = Line(Vector3(0, 0, 100), Vector3(10, 10, -90))
p = line.plane_intersection(plane)
assert p.close(Vector3(11.11, 11.11, 0.00), tol=1e-2)

if __name__ == '__main__':
unittest.main()