THRIFT-3221 Create a tool to audit network compatibility between two
.thrift files
Client: Compiler (general)
Patch: Sanjay Poojary <sanjay.poojary@ni.com>, Ben Craig
<bencraig@apache.org>, and Zach Hindes <zach.hindes@ni.com>

This closes #541
diff --git a/compiler/cpp/src/main.cc b/compiler/cpp/src/main.cc
index 97d523e..a337cc6 100644
--- a/compiler/cpp/src/main.cc
+++ b/compiler/cpp/src/main.cc
@@ -51,6 +51,7 @@
 #include "parse/t_program.h"
 #include "parse/t_scope.h"
 #include "generate/t_generator.h"
+#include "audit/t_audit.h"
 
 #include "version.h"
 
@@ -169,6 +170,17 @@
 bool gen_recurse = false;
 
 /**
+ * Flags to control thrift audit
+ */
+bool g_audit = false;
+
+/**
+ * Flag to control return status
+ */
+bool g_return_failure = false;
+bool g_audit_fatal = true;
+
+/**
  * Win32 doesn't have realpath, so use fallback implementation in that case,
  * otherwise this just calls through to realpath
  */
@@ -711,6 +723,13 @@
   fprintf(stderr, "                Keys and values are options passed to the generator.\n");
   fprintf(stderr, "                Many options will not require values.\n");
   fprintf(stderr, "\n");
+  fprintf(stderr, "Options related to audit operation\n");
+  fprintf(stderr, "   --audit OldFile   Old Thrift file to be audited with 'file'\n");
+  fprintf(stderr, "  -Iold dir    Add a directory to the list of directories\n");
+  fprintf(stderr, "                searched for include directives for old thrift file\n");
+  fprintf(stderr, "  -Inew dir    Add a directory to the list of directories\n");
+  fprintf(stderr, "                searched for include directives for new thrift file\n");
+  fprintf(stderr, "\n");
   fprintf(stderr, "Available generators (and options):\n");
 
   t_generator_registry::gen_map_t gen_map = t_generator_registry::get_generator_map();
@@ -1029,6 +1048,30 @@
   }
 }
 
+void audit(t_program* new_program, t_program* old_program, string new_thrift_include_path, string old_thrift_include_path)
+{
+  vector<string> temp_incl_searchpath = g_incl_searchpath;
+  if(!old_thrift_include_path.empty()) {
+    g_incl_searchpath.push_back(old_thrift_include_path);
+  }
+
+  parse(old_program, NULL);
+
+  g_incl_searchpath = temp_incl_searchpath;
+  if(!new_thrift_include_path.empty()) {
+    g_incl_searchpath.push_back(new_thrift_include_path);
+  }
+
+  parse(new_program, NULL);
+
+  compare_namespace(new_program, old_program);
+  compare_services(new_program->get_services(), old_program->get_services());
+  compare_enums(new_program->get_enums(), old_program->get_enums());
+  compare_structs(new_program->get_structs(), old_program->get_structs());
+  compare_structs(new_program->get_xceptions(), old_program->get_xceptions());
+  compare_consts(new_program->get_consts(), old_program->get_consts());
+}
+
 /**
  * Parse it up.. then spit it back out, in pretty much every language. Alright
  * not that many languages, but the cool ones that we care about.
@@ -1049,6 +1092,9 @@
   }
 
   vector<string> generator_strings;
+  string old_thrift_include_path;
+  string new_thrift_include_path;
+  string old_input_file;
 
   // Set the current path to a dummy value to make warning messages clearer.
   g_curpath = "arguments";
@@ -1118,6 +1164,35 @@
 #endif
         if (!check_is_directory(out_path.c_str()))
           return -1;
+      } else if (strcmp(arg, "-audit") == 0) {
+        g_audit = true;
+        arg = argv[++i];
+        if (arg == NULL) {
+          fprintf(stderr, "Missing old thrift file name for audit operation\n");
+          usage();
+        }
+        char old_thrift_file_rp[THRIFT_PATH_MAX];
+
+        if (saferealpath(arg, old_thrift_file_rp) == NULL) {
+          failure("Could not open input file with realpath: %s", arg);
+        }
+        old_input_file = string(old_thrift_file_rp);
+      } else if(strcmp(arg, "-audit-nofatal") == 0){
+        g_audit_fatal = false;
+      } else if (strcmp(arg, "-Iold") == 0) {
+        arg = argv[++i];
+        if (arg == NULL) {
+          fprintf(stderr, "Missing Include directory for old thrift file\n");
+          usage();
+        }
+        old_thrift_include_path = string(arg);
+      } else if (strcmp(arg, "-Inew") == 0) {
+        arg = argv[++i];
+        if(arg == NULL) {
+        fprintf(stderr, "Missing Include directory for new thrift file\n");
+        usage();
+        }
+        new_thrift_include_path = string(arg);
       } else {
         fprintf(stderr, "Unrecognized option: %s\n", arg);
         usage();
@@ -1139,41 +1214,6 @@
     exit(0);
   }
 
-  // You gotta generate something!
-  if (generator_strings.empty()) {
-    fprintf(stderr, "No output language(s) specified\n");
-    usage();
-  }
-
-  // Real-pathify it
-  char rp[THRIFT_PATH_MAX];
-  if (argv[i] == NULL) {
-    fprintf(stderr, "Missing file name\n");
-    usage();
-  }
-  if (saferealpath(argv[i], rp) == NULL) {
-    failure("Could not open input file with realpath: %s", argv[i]);
-  }
-  string input_file(rp);
-
-  // Instance of the global parse tree
-  t_program* program = new t_program(input_file);
-  if (out_path.size()) {
-    program->set_out_path(out_path, out_path_is_absolute);
-  }
-
-  // Compute the cpp include prefix.
-  // infer this from the filename passed in
-  string input_filename = argv[i];
-  string include_prefix;
-
-  string::size_type last_slash = string::npos;
-  if ((last_slash = input_filename.rfind("/")) != string::npos) {
-    include_prefix = input_filename.substr(0, last_slash);
-  }
-
-  program->set_include_prefix(include_prefix);
-
   // Initialize global types
   g_type_void = new t_base_type("void", t_base_type::TYPE_VOID);
   g_type_string = new t_base_type("string", t_base_type::TYPE_STRING);
@@ -1188,24 +1228,87 @@
   g_type_i64 = new t_base_type("i64", t_base_type::TYPE_I64);
   g_type_double = new t_base_type("double", t_base_type::TYPE_DOUBLE);
 
-  // Parse it!
-  parse(program, NULL);
+  if(g_audit)
+  {
+    // Audit operation
 
-  // The current path is not really relevant when we are doing generation.
-  // Reset the variable to make warning messages clearer.
-  g_curpath = "generation";
-  // Reset yylineno for the heck of it.  Use 1 instead of 0 because
-  // That is what shows up during argument parsing.
-  yylineno = 1;
+    if (old_input_file.empty()) {
+      fprintf(stderr, "Missing file name of old thrift file for audit\n");
+      usage();
+    }
 
-  // Generate it!
-  generate(program, generator_strings);
+    char new_thrift_file_rp[THRIFT_PATH_MAX];
+    if (argv[i] == NULL) {
+      fprintf(stderr, "Missing file name of new thrift file for audit\n");
+      usage();
+    }
+    if (saferealpath(argv[i], new_thrift_file_rp) == NULL) {
+      failure("Could not open input file with realpath: %s", argv[i]);
+    }
+    string new_input_file(new_thrift_file_rp);
+
+    t_program new_program(new_input_file);
+    t_program old_program(old_input_file);
+
+    audit(&new_program, &old_program, new_thrift_include_path, old_thrift_include_path);
+
+  } else {
+    // Generate options
+    
+    // You gotta generate something!
+    if (generator_strings.empty()) {
+      fprintf(stderr, "No output language(s) specified\n");
+      usage();
+    }
+
+    // Real-pathify it
+    char rp[THRIFT_PATH_MAX];
+    if (argv[i] == NULL) {
+      fprintf(stderr, "Missing file name\n");
+      usage();
+    }
+    if (saferealpath(argv[i], rp) == NULL) {
+      failure("Could not open input file with realpath: %s", argv[i]);
+    }
+    string input_file(rp);
+
+    // Instance of the global parse tree
+    t_program* program = new t_program(input_file);
+    if (out_path.size()) {
+      program->set_out_path(out_path, out_path_is_absolute);
+    }
+
+    // Compute the cpp include prefix.
+    // infer this from the filename passed in
+    string input_filename = argv[i];
+    string include_prefix;
+
+    string::size_type last_slash = string::npos;
+    if ((last_slash = input_filename.rfind("/")) != string::npos) {
+      include_prefix = input_filename.substr(0, last_slash);
+    }
+
+    program->set_include_prefix(include_prefix);
+
+    // Parse it!
+    parse(program, NULL);
+
+    // The current path is not really relevant when we are doing generation.
+    // Reset the variable to make warning messages clearer.
+    g_curpath = "generation";
+    // Reset yylineno for the heck of it.  Use 1 instead of 0 because
+    // That is what shows up during argument parsing.
+    yylineno = 1;
+
+    // Generate it!
+    generate(program, generator_strings);
+    delete program;
+  }
 
   // Clean up. Who am I kidding... this program probably orphans heap memory
   // all over the place, but who cares because it is about to exit and it is
   // all referenced and used by this wacky parse tree up until now anyways.
 
-  delete program;
   delete g_type_void;
   delete g_type_string;
   delete g_type_bool;
@@ -1216,5 +1319,9 @@
   delete g_type_double;
 
   // Finished
+  if (g_return_failure && g_audit_fatal) {
+    exit(2);
+  }
+  // Finished
   return 0;
 }