Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
0d850cd8d7 | |||
cc8d0c3913 | |||
50fc79dc42 | |||
8b00d6ed07 | |||
f13dc5e166 | |||
f8cd24f0ef | |||
5ffa607322 | |||
cadf3d1b38 |
@ -31,7 +31,17 @@ PubSubClient client(server, 1883, callback, ethClient);
|
||||
|
||||
// Callback function
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
client.publish("outTopic", payload, length);
|
||||
// In order to republish this payload, a copy must be made
|
||||
// as the orignal payload buffer will be overwritten whilst
|
||||
// constructing the PUBLISH packet.
|
||||
|
||||
// Allocate the correct amount of memory for the payload copy
|
||||
byte* p = (byte*)malloc(length);
|
||||
// Copy the payload to the new buffer
|
||||
memcpy(p,payload,length);
|
||||
client.publish("outTopic", p, length);
|
||||
// Free the memory
|
||||
free(p);
|
||||
}
|
||||
|
||||
void setup()
|
||||
|
4
tests/.gitignore
vendored
Normal file
4
tests/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.build
|
||||
tmpbin
|
||||
logs
|
||||
*.pyc
|
60
tests/README.md
Normal file
60
tests/README.md
Normal file
@ -0,0 +1,60 @@
|
||||
## Arduino Client for MQTT Test Suite
|
||||
|
||||
This is a regression test suite for the `PubSubClient` library.
|
||||
|
||||
It is a work-in-progress and is subject to complete refactoring as the whim takes
|
||||
me.
|
||||
|
||||
Without a suitable arduino plugged in, the test suite will only check the
|
||||
example sketches compile cleanly against the library.
|
||||
|
||||
With an arduino plugged in, each sketch that has a corresponding python
|
||||
test case is built, uploaded and then the tests run.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Python 2.7+
|
||||
- [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches
|
||||
|
||||
## Running
|
||||
|
||||
The test suite _does not_ run an MQTT server - it is assumed to be running already.
|
||||
|
||||
$ python testsuite.py
|
||||
|
||||
A summary of activity is printed to the console. More comprehensive logs are written
|
||||
to the `logs` directory.
|
||||
|
||||
## What it does
|
||||
|
||||
For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case
|
||||
`testcases/mqtt_basic.py`.
|
||||
|
||||
The test case must follow these conventions:
|
||||
- sub-class `unittest.TestCase`
|
||||
- provide the class methods `setUpClass` and `tearDownClass` (TODO: make this optional)
|
||||
- all test method names begin with `test_`
|
||||
|
||||
The suite will call the `setUpClass` method _before_ uploading the sketch. This
|
||||
allows any test setup to be performed before the sketch runs - such as connecting
|
||||
a client and subscribing to topics.
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
The file `testcases/settings.py` is used to config the test environment.
|
||||
|
||||
- `server_ip` - the IP address of the broker the client should connect to (the broker port is assumed to be 1883).
|
||||
- `arduino_ip` - the IP address the arduino should use (when not testing DHCP).
|
||||
|
||||
Before each sketch is compiled, these values are automatically substituted in. To
|
||||
do this, the suite looks for lines that _start_ with the following:
|
||||
|
||||
byte server[] = {
|
||||
byte ip[] = {
|
||||
|
||||
and replaces them with the appropriate values.
|
||||
|
||||
|
||||
|
||||
|
0
tests/testcases/__init__.py
Normal file
0
tests/testcases/__init__.py
Normal file
43
tests/testcases/mqtt_basic.py
Normal file
43
tests/testcases/mqtt_basic.py
Normal file
@ -0,0 +1,43 @@
|
||||
import unittest
|
||||
import settings
|
||||
|
||||
import time
|
||||
import mosquitto
|
||||
|
||||
import serial
|
||||
|
||||
def on_message(mosq, obj, msg):
|
||||
obj.message_queue.append(msg)
|
||||
|
||||
class mqtt_basic(unittest.TestCase):
|
||||
|
||||
message_queue = []
|
||||
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True,obj=self)
|
||||
self.client.connect(settings.server_ip)
|
||||
self.client.on_message = on_message
|
||||
self.client.subscribe("outTopic",0)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
self.client.disconnect()
|
||||
|
||||
def test_one(self):
|
||||
i=30
|
||||
while len(self.message_queue) == 0 and i > 0:
|
||||
self.client.loop()
|
||||
time.sleep(0.5)
|
||||
i -= 1
|
||||
self.assertTrue(i>0, "message receive timed-out")
|
||||
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
|
||||
msg = self.message_queue[0]
|
||||
self.assertEqual(msg.mid,0,"message id not 0")
|
||||
self.assertEqual(msg.topic,"outTopic","message topic incorrect")
|
||||
self.assertEqual(msg.payload,"hello world")
|
||||
self.assertEqual(msg.qos,0,"message qos not 0")
|
||||
self.assertEqual(msg.retain,False,"message retain flag incorrect")
|
||||
|
||||
|
||||
|
64
tests/testcases/mqtt_publish_in_callback.py
Normal file
64
tests/testcases/mqtt_publish_in_callback.py
Normal file
@ -0,0 +1,64 @@
|
||||
import unittest
|
||||
import settings
|
||||
|
||||
import time
|
||||
import mosquitto
|
||||
|
||||
import serial
|
||||
|
||||
def on_message(mosq, obj, msg):
|
||||
obj.message_queue.append(msg)
|
||||
|
||||
class mqtt_publish_in_callback(unittest.TestCase):
|
||||
|
||||
message_queue = []
|
||||
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True,obj=self)
|
||||
self.client.connect(settings.server_ip)
|
||||
self.client.on_message = on_message
|
||||
self.client.subscribe("outTopic",0)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
self.client.disconnect()
|
||||
|
||||
def test_connect(self):
|
||||
i=30
|
||||
while len(self.message_queue) == 0 and i > 0:
|
||||
self.client.loop()
|
||||
time.sleep(0.5)
|
||||
i -= 1
|
||||
self.assertTrue(i>0, "message receive timed-out")
|
||||
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
|
||||
msg = self.message_queue.pop(0)
|
||||
self.assertEqual(msg.mid,0,"message id not 0")
|
||||
self.assertEqual(msg.topic,"outTopic","message topic incorrect")
|
||||
self.assertEqual(msg.payload,"hello world")
|
||||
self.assertEqual(msg.qos,0,"message qos not 0")
|
||||
self.assertEqual(msg.retain,False,"message retain flag incorrect")
|
||||
|
||||
|
||||
def test_publish(self):
|
||||
self.assertEqual(len(self.message_queue), 0, "message queue not empty")
|
||||
payload = "abcdefghij"
|
||||
self.client.publish("inTopic",payload)
|
||||
|
||||
i=30
|
||||
while len(self.message_queue) == 0 and i > 0:
|
||||
self.client.loop()
|
||||
time.sleep(0.5)
|
||||
i -= 1
|
||||
|
||||
self.assertTrue(i>0, "message receive timed-out")
|
||||
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
|
||||
msg = self.message_queue.pop(0)
|
||||
self.assertEqual(msg.mid,0,"message id not 0")
|
||||
self.assertEqual(msg.topic,"outTopic","message topic incorrect")
|
||||
self.assertEqual(msg.payload,payload)
|
||||
self.assertEqual(msg.qos,0,"message qos not 0")
|
||||
self.assertEqual(msg.retain,False,"message retain flag incorrect")
|
||||
|
||||
|
||||
|
2
tests/testcases/settings.py
Normal file
2
tests/testcases/settings.py
Normal file
@ -0,0 +1,2 @@
|
||||
server_ip = "172.16.0.2"
|
||||
arduino_ip = "172.16.0.100"
|
179
tests/testsuite.py
Normal file
179
tests/testsuite.py
Normal file
@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import shutil
|
||||
from subprocess import call
|
||||
import importlib
|
||||
import unittest
|
||||
import re
|
||||
|
||||
from testcases import settings
|
||||
|
||||
class Workspace(object):
|
||||
|
||||
def __init__(self):
|
||||
self.root_dir = os.getcwd()
|
||||
self.build_dir = os.path.join(self.root_dir,"tmpbin");
|
||||
self.log_dir = os.path.join(self.root_dir,"logs");
|
||||
self.tests_dir = os.path.join(self.root_dir,"testcases");
|
||||
self.examples_dir = os.path.join(self.root_dir,"../PubSubClient/examples")
|
||||
self.examples = []
|
||||
self.tests = []
|
||||
if not os.path.isdir("../PubSubClient"):
|
||||
raise Exception("Cannot find PubSubClient library")
|
||||
try:
|
||||
import ino
|
||||
except:
|
||||
raise Exception("ino tool not installed")
|
||||
|
||||
def init(self):
|
||||
if os.path.isdir(self.build_dir):
|
||||
shutil.rmtree(self.build_dir)
|
||||
os.mkdir(self.build_dir)
|
||||
if os.path.isdir(self.log_dir):
|
||||
shutil.rmtree(self.log_dir)
|
||||
os.mkdir(self.log_dir)
|
||||
|
||||
os.chdir(self.build_dir)
|
||||
call(["ino","init"])
|
||||
|
||||
shutil.copytree("../../PubSubClient","lib/PubSubClient")
|
||||
|
||||
filenames = []
|
||||
for root, dirs, files in os.walk(self.examples_dir):
|
||||
filenames += [os.path.join(root,f) for f in files if f.endswith(".ino")]
|
||||
filenames.sort()
|
||||
for e in filenames:
|
||||
self.examples.append(Sketch(self,e))
|
||||
|
||||
filenames = []
|
||||
for root, dirs, files in os.walk(self.tests_dir):
|
||||
filenames += [os.path.join(root,f) for f in files if f.endswith(".ino")]
|
||||
filenames.sort()
|
||||
for e in filenames:
|
||||
self.tests.append(Sketch(self,e))
|
||||
|
||||
def clean(self):
|
||||
shutil.rmtree(self.build_dir)
|
||||
|
||||
class Sketch(object):
|
||||
def __init__(self,wksp,fn):
|
||||
self.w = wksp
|
||||
self.filename = fn
|
||||
self.basename = os.path.basename(self.filename)
|
||||
self.build_log = os.path.join(self.w.log_dir,"%s.log"%(os.path.basename(self.filename),))
|
||||
self.build_err_log = os.path.join(self.w.log_dir,"%s.err.log"%(os.path.basename(self.filename),))
|
||||
self.build_upload_log = os.path.join(self.w.log_dir,"%s.upload.log"%(os.path.basename(self.filename),))
|
||||
|
||||
def build(self):
|
||||
sys.stdout.write(" Build: ")
|
||||
sys.stdout.flush()
|
||||
|
||||
# Copy sketch over, replacing IP addresses as necessary
|
||||
fin = open(self.filename,"r")
|
||||
lines = fin.readlines()
|
||||
fin.close()
|
||||
fout = open(os.path.join(self.w.build_dir,"src","sketch.ino"),"w")
|
||||
for l in lines:
|
||||
if re.match(r"^byte server\[\] = {",l):
|
||||
fout.write("byte server[] = { %s };\n"%(settings.server_ip.replace(".",", "),))
|
||||
elif re.match(r"^byte ip\[\] = {",l):
|
||||
fout.write("byte ip[] = { %s };\n"%(settings.arduino_ip.replace(".",", "),))
|
||||
else:
|
||||
fout.write(l)
|
||||
fout.flush()
|
||||
fout.close()
|
||||
|
||||
# Run build
|
||||
fout = open(self.build_log, "w")
|
||||
ferr = open(self.build_err_log, "w")
|
||||
rc = call(["ino","build"],stdout=fout,stderr=ferr)
|
||||
fout.close()
|
||||
ferr.close()
|
||||
if rc == 0:
|
||||
sys.stdout.write("pass")
|
||||
sys.stdout.write("\n")
|
||||
return True
|
||||
else:
|
||||
sys.stdout.write("fail")
|
||||
sys.stdout.write("\n")
|
||||
with open(self.build_err_log) as f:
|
||||
for line in f:
|
||||
print " ",line,
|
||||
return False
|
||||
|
||||
def upload(self):
|
||||
sys.stdout.write(" Upload: ")
|
||||
sys.stdout.flush()
|
||||
fout = open(self.build_upload_log, "w")
|
||||
rc = call(["ino","upload"],stdout=fout,stderr=fout)
|
||||
fout.close()
|
||||
if rc == 0:
|
||||
sys.stdout.write("pass")
|
||||
sys.stdout.write("\n")
|
||||
return True
|
||||
else:
|
||||
sys.stdout.write("fail")
|
||||
sys.stdout.write("\n")
|
||||
with open(self.build_upload_log) as f:
|
||||
for line in f:
|
||||
print " ",line,
|
||||
return False
|
||||
|
||||
|
||||
def test(self):
|
||||
# import the matching test case, if it exists
|
||||
try:
|
||||
basename = os.path.basename(self.filename)[:-4]
|
||||
i = importlib.import_module("testcases."+basename)
|
||||
except:
|
||||
sys.stdout.write(" Test: no tests found")
|
||||
sys.stdout.write("\n")
|
||||
return
|
||||
c = getattr(i,basename)
|
||||
|
||||
testmethods = [m for m in dir(c) if m.startswith("test_")]
|
||||
testmethods.sort()
|
||||
tests = []
|
||||
for m in testmethods:
|
||||
tests.append(c(m))
|
||||
|
||||
result = unittest.TestResult()
|
||||
c.setUpClass()
|
||||
if self.upload():
|
||||
sys.stdout.write(" Test: ")
|
||||
sys.stdout.flush()
|
||||
for t in tests:
|
||||
t.run(result)
|
||||
print "%d/%d"%(result.testsRun-len(result.failures)-len(result.errors),result.testsRun)
|
||||
if not result.wasSuccessful():
|
||||
if len(result.failures) > 0:
|
||||
for f in result.failures:
|
||||
print "-- %s"%(str(f[0]),)
|
||||
print f[1]
|
||||
if len(result.errors) > 0:
|
||||
print " Errors:"
|
||||
for f in result.errors:
|
||||
print "-- %s"%(str(f[0]),)
|
||||
print f[1]
|
||||
c.tearDownClass()
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_tests = True
|
||||
|
||||
w = Workspace()
|
||||
w.init()
|
||||
|
||||
for e in w.examples:
|
||||
print "--------------------------------------"
|
||||
print "[%s]"%(e.basename,)
|
||||
if e.build() and run_tests:
|
||||
e.test()
|
||||
for e in w.tests:
|
||||
print "--------------------------------------"
|
||||
print "[%s]"%(e.basename,)
|
||||
if e.build() and run_tests:
|
||||
e.test()
|
||||
|
||||
w.clean()
|
Reference in New Issue
Block a user