aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--scripts/test_klippy.py138
-rwxr-xr-xscripts/travis-build.sh3
-rw-r--r--test/klippy/cartesian.test4
-rw-r--r--test/klippy/corexy.test4
-rw-r--r--test/klippy/delta.test33
-rw-r--r--test/klippy/move.gcode22
-rw-r--r--test/klippy/out_of_bounds.test8
-rw-r--r--test/klippy/printers.test40
8 files changed, 250 insertions, 2 deletions
diff --git a/scripts/test_klippy.py b/scripts/test_klippy.py
new file mode 100644
index 00000000..f70095b5
--- /dev/null
+++ b/scripts/test_klippy.py
@@ -0,0 +1,138 @@
+# Regression test helper script
+#
+# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+import sys, os, optparse, logging, subprocess
+
+
+######################################################################
+# Test cases
+######################################################################
+
+class error(Exception):
+ pass
+
+class TestCase:
+ def __init__(self, fname, dictdir, tempdir):
+ self.fname = fname
+ self.dictdir = dictdir
+ self.tempdir = tempdir
+ def relpath(self, fname, rel='test'):
+ if rel == 'dict':
+ reldir = self.dictdir
+ elif rel == 'temp':
+ reldir = self.tempdir
+ else:
+ reldir = os.path.dirname(self.fname)
+ return os.path.join(reldir, fname)
+ def parse_test(self):
+ # Parse file into test cases
+ config_fname = gcode_fname = dict_fname = None
+ should_fail = multi_tests = False
+ gcode = []
+ f = open(self.fname, 'rb')
+ for line in f:
+ cpos = line.find('#')
+ if cpos >= 0:
+ line = line[:cpos]
+ parts = line.strip().split()
+ if not parts:
+ continue
+ if parts[0] == "CONFIG":
+ if config_fname is not None:
+ # Multiple tests in same file
+ if not multi_tests:
+ multi_tests = True
+ self.launch_test(config_fname, dict_fname,
+ gcode_fname, gcode, should_fail)
+ config_fname = self.relpath(parts[1])
+ if multi_tests:
+ self.launch_test(config_fname, dict_fname,
+ gcode_fname, gcode, should_fail)
+ elif parts[0] == "DICTIONARY":
+ dict_fname = self.relpath(parts[1], 'dict')
+ elif parts[0] == "GCODE":
+ gcode_fname = self.relpath(parts[1])
+ elif parts[0] == "SHOULD_FAIL":
+ should_fail = True
+ else:
+ gcode.append(line)
+ f.close()
+ if not multi_tests:
+ self.launch_test(config_fname, dict_fname,
+ gcode_fname, gcode, should_fail)
+ def launch_test(self, config_fname, dict_fname, gcode_fname, gcode,
+ should_fail):
+ gcode_is_temp = False
+ if gcode_fname is None:
+ gcode_fname = self.relpath("_test_.gcode", 'temp')
+ gcode_is_temp = True
+ f = open(gcode_fname, 'wb')
+ f.write('\n'.join(gcode))
+ f.close()
+ elif gcode:
+ raise error("Can't specify both a gcode file and gcode commands")
+ if config_fname is None:
+ raise error("config file not specified")
+ if dict_fname is None:
+ raise error("data dictionary file not specified")
+ # Call klippy
+ sys.stderr.write("\n Starting %s (%s)\n" % (
+ self.fname, os.path.basename(config_fname)))
+ args = [sys.executable, './klippy/klippy.py', config_fname,
+ '-i', gcode_fname, '-o', '/dev/null',
+ '-d', dict_fname
+ ]
+ res = subprocess.call(args)
+ if should_fail:
+ if not res:
+ raise error("Test failed to raise and error")
+ else:
+ if res:
+ raise error("Error during test")
+ # Do cleanup
+ if gcode_is_temp:
+ os.unlink(gcode_fname)
+ def run(self):
+ try:
+ self.parse_test()
+ except error as e:
+ return str(e)
+ except Exception:
+ logging.exception("Unhandled exception during test run")
+ return "internal error"
+ return "success"
+
+
+######################################################################
+# Startup
+######################################################################
+
+def main():
+ # Parse args
+ usage = "%prog [options] <test cases>"
+ opts = optparse.OptionParser(usage)
+ opts.add_option("-d", "--dictdir", dest="dictdir", default=".",
+ help="directory for dictionary files")
+ opts.add_option("-t", "--tempdir", dest="tempdir", default=".",
+ help="directory for temporary files")
+ options, args = opts.parse_args()
+ if len(args) < 1:
+ opts.error("Incorrect number of arguments")
+ logging.basicConfig(level=logging.DEBUG)
+ dictdir = options.dictdir
+ tempdir = options.tempdir
+
+ # Run each test
+ for fname in args:
+ tc = TestCase(fname, dictdir, tempdir)
+ res = tc.run()
+ if res != 'success':
+ sys.stderr.write("\n\nTest case %s FAILED (%s)!\n\n" % (fname, res))
+ sys.exit(-1)
+
+ sys.stderr.write("\n All %d test cases passed\n" % (len(args),))
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/travis-build.sh b/scripts/travis-build.sh
index 1fe45f1e..f65c50f6 100755
--- a/scripts/travis-build.sh
+++ b/scripts/travis-build.sh
@@ -39,6 +39,5 @@ mkdir -p ${HOSTDIR}
echo "travis_fold:start:klippy"
echo "=============== Test invoke klippy"
-$PYTHON klippy/klippy.py config/example.cfg -i /dev/null -o ${HOSTDIR}/output -v -d ${DICTDIR}/atmega2560-16mhz.dict
-$PYTHON klippy/parsedump.py ${DICTDIR}/atmega2560-16mhz.dict ${HOSTDIR}/output > ${HOSTDIR}/output-parsed
+$PYTHON scripts/test_klippy.py -d ${DICTDIR} test/klippy/*.test
echo "travis_fold:end:klippy"
diff --git a/test/klippy/cartesian.test b/test/klippy/cartesian.test
new file mode 100644
index 00000000..42b00ca7
--- /dev/null
+++ b/test/klippy/cartesian.test
@@ -0,0 +1,4 @@
+# Test case for basic cartesian movement
+CONFIG ../../config/example.cfg
+DICTIONARY atmega2560-16mhz.dict
+GCODE move.gcode
diff --git a/test/klippy/corexy.test b/test/klippy/corexy.test
new file mode 100644
index 00000000..6e43bdf8
--- /dev/null
+++ b/test/klippy/corexy.test
@@ -0,0 +1,4 @@
+# Test case for basic corexy movement
+CONFIG ../../config/example-corexy.cfg
+DICTIONARY atmega2560-16mhz.dict
+GCODE move.gcode
diff --git a/test/klippy/delta.test b/test/klippy/delta.test
new file mode 100644
index 00000000..0cd132b1
--- /dev/null
+++ b/test/klippy/delta.test
@@ -0,0 +1,33 @@
+# Test case for basic movement on delta printers
+CONFIG ../../config/example-delta.cfg
+DICTIONARY atmega2560-16mhz.dict
+
+# Start by homing the printer. Also tests Z moves.
+G28
+
+# Perform an XY+Z move with infintesimal XY component
+G1 x0 y0 z15
+
+# Perform an XY move along Y axis (aligned with rear tower)
+G1 x0 y5 z15
+
+# Perform an XY+Z move along Y axis
+G1 x0 y-5 z10
+
+# Perform a Z move
+G1 x0 y-5 z15
+
+# Perform an XY move across all three towers
+G1 x2 y2 z10
+
+# Perform an XY+Z move with tiny Z movement
+G1 x2 y-10 z10.1
+
+# Move to far away position
+G1 x140 y0
+
+# Move to extreme position
+G1 x145 y0
+
+# Move to another extreme position
+G1 x145 y5
diff --git a/test/klippy/move.gcode b/test/klippy/move.gcode
new file mode 100644
index 00000000..30e2a02c
--- /dev/null
+++ b/test/klippy/move.gcode
@@ -0,0 +1,22 @@
+; Simple movement tests
+
+; Start by homing the printer.
+G28
+G1 F6000
+
+; Z / X / Y moves
+G1 Z1
+G1 X1
+G1 Y1
+
+; diagonal moves
+G1 X0 Y0
+G1 X1 Z2
+G1 X0 Y1 Z1
+
+; extrude only moves
+G1 E1
+G1 E0
+
+; regular extrude move
+G1 X0 Y0 E.01
diff --git a/test/klippy/out_of_bounds.test b/test/klippy/out_of_bounds.test
new file mode 100644
index 00000000..5375ea7e
--- /dev/null
+++ b/test/klippy/out_of_bounds.test
@@ -0,0 +1,8 @@
+# Test that basic bounds checks work
+CONFIG ../../config/example.cfg
+DICTIONARY atmega2560-16mhz.dict
+SHOULD_FAIL
+
+# Home the printer, and then attempt to move to an obviously bad location
+G28
+G1 Y9999
diff --git a/test/klippy/printers.test b/test/klippy/printers.test
new file mode 100644
index 00000000..0f315ac3
--- /dev/null
+++ b/test/klippy/printers.test
@@ -0,0 +1,40 @@
+# Basic sanity checks on the example printer config files
+GCODE move.gcode
+
+# Printers using the atmega2560
+DICTIONARY atmega2560-16mhz.dict
+CONFIG ../../config/generic-einsy-rambo.cfg
+CONFIG ../../config/generic-mini-rambo.cfg
+CONFIG ../../config/generic-rambo.cfg
+CONFIG ../../config/generic-ramps.cfg
+CONFIG ../../config/generic-rumba.cfg
+CONFIG ../../config/printer-anycubic-i3-mega-2017.cfg
+CONFIG ../../config/printer-anycubic-kossel-2016.cfg
+CONFIG ../../config/printer-creality-cr10s-2017.cfg
+CONFIG ../../config/printer-lulzbot-taz6-2017.cfg
+CONFIG ../../config/printer-makergear-m2-2012.cfg
+CONFIG ../../config/printer-seemecnc-rostock-max-v2-2015.cfg
+CONFIG ../../config/printer-wanhao-duplicator-i3-plus-2017.cfg
+
+# Printers using the atmega1284p
+DICTIONARY atmega1284p.dict
+CONFIG ../../config/generic-melzi.cfg
+CONFIG ../../config/printer-anet-a8-2017.cfg
+CONFIG ../../config/printer-anet-e10-2018.cfg
+CONFIG ../../config/printer-creality-cr10-2017.cfg
+CONFIG ../../config/printer-creality-cr10mini-2017.cfg
+CONFIG ../../config/printer-creality-ender3-2018.cfg
+CONFIG ../../config/printer-tronxy-x5s-2018.cfg
+CONFIG ../../config/printer-wanhao-duplicator-i3-v2.1-2017.cfg
+
+# Printers using the at90usb1286
+DICTIONARY at90usb1286.dict
+CONFIG ../../config/generic-printrboard.cfg
+
+# Printers using the sam3x8e
+DICTIONARY sam3x8e.dict
+CONFIG ../../config/generic-radds.cfg
+
+# Printers using the lpc176x
+DICTIONARY lpc176x.dict
+CONFIG ../../config/generic-smoothieboard.cfg