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()