diff --git a/ndiff/docs/ndiff.1 b/ndiff/docs/ndiff.1
index 1ab083471..aedbdbb56 100644
--- a/ndiff/docs/ndiff.1
+++ b/ndiff/docs/ndiff.1
@@ -2,12 +2,12 @@
.\" Title: ndiff
.\" Author: [see the "Authors" section]
.\" Generator: DocBook XSL Stylesheets v1.74.3
-.\" Date: 07/22/2009
+.\" Date: 07/30/2009
.\" Manual: User Commands
.\" Source: Ndiff
.\" Language: English
.\"
-.TH "NDIFF" "1" "07/22/2009" "Ndiff" "User Commands"
+.TH "NDIFF" "1" "07/30/2009" "Ndiff" "User Commands"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -370,6 +370,44 @@ If the script is saved as
.RE
.\}
.sp
+.SH "EXIT CODE"
+.PP
+The exit code indicates whether the scans are equal\&.
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+0 means that the scans are the same in all the aspects Ndiff knows about\&.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+1 means that the scans differ\&.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+2 indicates a runtime error, such as the failure to open a file\&.
+.RE
+.sp
+.RE
.SH "BUGS"
.PP
Report bugs to the
diff --git a/ndiff/docs/ndiff.xml b/ndiff/docs/ndiff.xml
index 816eec4a0..afe844999 100644
--- a/ndiff/docs/ndiff.xml
+++ b/ndiff/docs/ndiff.xml
@@ -355,6 +355,21 @@ ln -sf scan-$date.xml scan-prev.xml
+
+ Exit Code
+
+
+ The exit code indicates whether the scans are equal.
+
+ 0 means that the scans are the same in all the
+ aspects Ndiff knows about.
+ 1 means that the scans differ.
+ 2 indicates a runtime error, such as the failure
+ to open a file.
+
+
+
+
Bugs
diff --git a/ndiff/ndiff b/ndiff/ndiff
index 466ef7981..277052edc 100755
--- a/ndiff/ndiff
+++ b/ndiff/ndiff
@@ -446,6 +446,8 @@ class ScanDiff(object):
document.writexml(f, addindent = u" ", newl = u"\n", encoding = "UTF-8")
document.unlink()
+ cost = property(lambda self: sum([hd.cost for hd in self.host_diffs.values()]))
+
class HostDiff(object):
"""A diff of two Hosts. It contains the two hosts, variables describing what
changed, and a list of PortDiffs and OS differences."""
@@ -1150,10 +1152,14 @@ service and OS detection.
--xml display output in XML format\
""" % sys.argv[0]
+EXIT_EQUAL = 0
+EXIT_DIFFERENT = 1
+EXIT_ERROR = 2
+
def usage_error(msg):
print >> sys.stderr, u"%s: %s" % (sys.argv[0], msg)
print >> sys.stderr, u"Try '%s -h' for help." % sys.argv[0]
- sys.exit(1)
+ sys.exit(EXIT_ERROR)
def main():
global verbose
@@ -1199,5 +1205,17 @@ def main():
elif output_format == "xml":
diff.print_xml()
+ if diff.cost == 0:
+ return EXIT_EQUAL
+ else:
+ return EXIT_DIFFERENT
+
+# Catch uncaught exceptions so they can produce an exit code of 2 (EXIT_ERROR),
+# not 1 like they would by default.
+def excepthook(type, value, tb):
+ sys.__excepthook__(type, value, tb)
+ sys.exit(EXIT_ERROR)
+
if __name__ == "__main__":
- main()
+ sys.excepthook = excepthook
+ sys.exit(main())
diff --git a/ndiff/ndifftest.py b/ndiff/ndifftest.py
index ed47bd69f..c6a08e60c 100755
--- a/ndiff/ndifftest.py
+++ b/ndiff/ndifftest.py
@@ -2,6 +2,7 @@
# Unit tests for Ndiff.
+import subprocess
import unittest
import xml.dom.minidom
import StringIO
@@ -688,4 +689,43 @@ def host_apply_diff(host, diff):
host.script_results[host.script_results.index(sr_a)] = sr_b
host.script_results.sort()
+def call_quiet(args, **kwargs):
+ """Run a command with subprocess.call and hide its output."""
+ return subprocess.call(args, stdout = subprocess.PIPE,
+ stderr = subprocess.STDOUT, **kwargs)
+
+class exit_code_test(unittest.TestCase):
+ NDIFF = "./ndiff"
+
+ def test_exit_equal(self):
+ """Test that the exit code is 0 when the diff is empty."""
+ for format in ("--text", "--xml"):
+ code = call_quiet([self.NDIFF, format,
+ "test-scans/simple.xml", "test-scans/simple.xml"])
+ self.assertEqual(code, 0)
+ # Should be independent of verbosity.
+ for format in ("--text", "--xml"):
+ code = call_quiet([self.NDIFF, "-v", format,
+ "test-scans/simple.xml", "test-scans/simple.xml"])
+ self.assertEqual(code, 0)
+
+ def test_exit_different(self):
+ """Test that the exit code is 1 when the diff is not empty."""
+ for format in ("--text", "--xml"):
+ code = call_quiet([self.NDIFF, format,
+ "test-scans/simple.xml", "test-scans/complex.xml"])
+ self.assertEqual(code, 1)
+
+ def test_exit_error(self):
+ """Test that the exit code is 2 when there is an error."""
+ code = call_quiet([self.NDIFF])
+ self.assertEqual(code, 2)
+ code = call_quiet([self.NDIFF, "test-scans/simple.xml"])
+ self.assertEqual(code, 2)
+ code = call_quiet([self.NDIFF, "test-scans/simple.xml",
+ "test-scans/nonexistent.xml"])
+ self.assertEqual(code, 2)
+ code = call_quiet([self.NDIFF, "--nothing"])
+ self.assertEqual(code, 2)
+
unittest.main()