Skip to content
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Furthermore, since the simulator has an 'oracle view' of the network, it allows

![](/img/route_plot.png)

# Tests

Unit tests can be executed by running `python3 -m unittest` from the root of the repo. Don't forget to activate your virtual env before running tests.

## License
Part of the source code is based on the work in [1], which eventually stems from [2]. The LoRaSim library from [2] can be found [here](https://www.lancaster.ac.uk/scc/sites/lora/lorasim.html).

Expand Down
Empty file added tests/__init__.py
Empty file.
107 changes: 107 additions & 0 deletions tests/test_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import unittest

import lib.common

class TestCommonFunctions(unittest.TestCase):

def test_calc_dist(self):
message = "sanity-checking our euclidean distance calculation"
# test some pythagorean triple triangles https://en.wikipedia.org/wiki/Pythagorean_triple
# (3, 4, 5)
# x diff: 3
# y diff: 4
p1 = (-1, -1)
p2 = (2, 3)
self.assertEqual(lib.common.calc_dist(p1[0], p2[0], p1[1], p2[1]), 5.0, message)

# (5, 12, 13)
# x diff: 5
# y diff: 12
p1 = (-1, -1)
p2 = (4, 11)
self.assertEqual(lib.common.calc_dist(p1[0], p2[0], p1[1], p2[1]), 13.0, message)

# test some pythagorean quadruple cuboids https://en.wikipedia.org/wiki/Pythagorean_quadruple
# (1, 2, 2, 3)
# x diff: 1
# y diff: 2
# z diff: 2
p1 = (-1, -1, -1)
p2 = (0, 1, 1)
self.assertEqual(lib.common.calc_dist(p1[0], p2[0], p1[1], p2[1], p1[2], p2[2]), 3.0, message)

# (2, 3, 6, 7)
# x diff: 2
# y diff: 3
# z diff: 6
p1 = (-1, -1, -1)
p2 = (1, 2, 5)
self.assertEqual(lib.common.calc_dist(p1[0], p2[0], p1[1], p2[1], p1[2], p2[2]), 7.0, message)

def test_find_random_position(self):
# mock up the needed objects
# conf: config from lib.config.Config(). Must have
# - XSIZE, YSIZE, OX, OY, MINDIST, FREQ, PTX, GL, current_preset property
# - MODEL, LPLD0, GAMMA, D0
# (just use an actual config object)
# nodes: empty list OR list of nodes which must have:
# - x, y attributes
from lib.config import Config
from lib.phy import estimate_path_loss

# TODO: iterate this test for each of our supported models, since they
# change the return value of estimate_path_loss. Also, each LoRa preset
# has its own sensitivity which changes radio range.
conf = Config()

class MyNode:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return f"MyNode(x={self.x}, y={self.y})"

lower_bound_x = conf.OX - conf.XSIZE/2
upper_bound_x = conf.OX + conf.XSIZE/2
lower_bound_y = conf.OY - conf.YSIZE/2
upper_bound_y = conf.OY + conf.YSIZE/2

nodes = []
# conditions that must be held:
# - found position can 'reach' at least one other node.
# - found position is not within conf.MINDIST of any other node.
# - found position is within defined scenario area.
# - a position is always returned

# first node case
position = lib.common.find_random_position(conf, nodes)
self.assertIsNotNone(position, "always return position")
self.assertGreaterEqual(position[0], lower_bound_x, f"x within bounds {position=}")
self.assertLessEqual(position[0], upper_bound_x, f"x within bounds {position=}")
self.assertGreaterEqual(position[1], lower_bound_y, f"y within bounds {position=}")
self.assertLessEqual(position[1], upper_bound_y, f"y within bounds {position=}")

# second node case
n = MyNode(0, 0)
nodes = [n]
position = lib.common.find_random_position(conf, nodes)
self.assertIsNotNone(position, "always return position")
self.assertGreaterEqual(position[0], lower_bound_x, f"x within bounds {position=}")
self.assertLessEqual(position[0], upper_bound_x, f"x within bounds {position=}")
self.assertGreaterEqual(position[1], lower_bound_y, f"y within bounds {position=}")
self.assertLessEqual(position[1], upper_bound_y, f"y within bounds {position=}")

distance = lib.common.calc_dist(n.x, position[0], n.y, position[1])
self.assertGreaterEqual(distance, conf.MINDIST, f"{position=} not within MINDIST of {n=}")

# this directly replicates the logic from the function which I dislike.
# Find a better way to test "found node can reach one other node",
# perhaps by pre-computing a max distance based on the config params
# we're using. There are lots of those, but they shouldn't change often.
pathLoss = estimate_path_loss(conf, distance, conf.FREQ)
rssi = conf.PTX + 2*conf.GL - pathLoss
self.assertGreaterEqual(rssi, conf.current_preset["sensitivity"], f"found {position=} is within radio range of {n=}")

if __name__ == '__main__':
unittest.main()
33 changes: 33 additions & 0 deletions tests/test_phy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import unittest

import lib.phy

class TestPhy(unittest.TestCase):

def test_rootFinder(self):
# double-check we can find the roots of some polynomials
message = "sanity-check Newton-Raphson root-finding implementation"
tolerance = 0.0000001

def poly1(x):
''' roots at x=-3, 0, 2.5 '''
return (x+3)*(x-2.5)*x

# should find -3
res = lib.phy.rootFinder(poly1, -3.5, tol=tolerance)
diff = abs(res - -3)
self.assertLess(diff, tolerance, message)

# should find 0
res = lib.phy.rootFinder(poly1, -1, tol=tolerance)
diff = abs(res - 0)
self.assertLess(diff, tolerance, message)

# should find 2.5
res = lib.phy.rootFinder(poly1, 3, tol=tolerance)
diff = abs(res - 2.5)
self.assertLess(diff, tolerance, message)


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