8 Commits
v1.9 ... v1.9.1

Author SHA1 Message Date
0d850cd8d7 update readme 2012-11-11 15:13:02 +00:00
cc8d0c3913 update readme 2012-11-11 15:09:23 +00:00
50fc79dc42 update readme 2012-11-11 15:05:27 +00:00
8b00d6ed07 update readme 2012-11-11 14:50:11 +00:00
f13dc5e166 add test cases 2012-11-11 14:17:50 +00:00
f8cd24f0ef rewrite testsuite in python 2012-11-08 21:45:41 +00:00
5ffa607322 Copy payload before republishing in mqtt_publish_in_callback example 2012-11-04 14:07:56 +00:00
cadf3d1b38 add test framework 2012-11-02 23:36:25 +00:00
8 changed files with 363 additions and 1 deletions

View File

@ -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
View File

@ -0,0 +1,4 @@
.build
tmpbin
logs
*.pyc

60
tests/README.md Normal file
View 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.

View File

View 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")

View 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")

View File

@ -0,0 +1,2 @@
server_ip = "172.16.0.2"
arduino_ip = "172.16.0.100"

179
tests/testsuite.py Normal file
View 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()