THRIFT-2628 struct member name conflicts due to lowercased names
Client: Erlang
Patch: Alisdair Sullivan

This closes #228
diff --git a/compiler/cpp/src/generate/t_erl_generator.cc b/compiler/cpp/src/generate/t_erl_generator.cc
index 2876788..0735ad1 100644
--- a/compiler/cpp/src/generate/t_erl_generator.cc
+++ b/compiler/cpp/src/generate/t_erl_generator.cc
@@ -54,9 +54,9 @@
   {
     (void) parsed_options;
     (void) option_string;
-    program_name_[0] = tolower(program_name_[0]);
-    service_name_[0] = tolower(service_name_[0]);
     out_dir_base_ = "gen-erl";
+
+    legacy_names_ = (parsed_options.find("legacynames") != parsed_options.end());
   }
 
   /**
@@ -103,7 +103,7 @@
    * Service-level generation functions
    */
 
-  void generate_service_helpers   (t_service*  tservice);
+  void generate_service_helpers   (t_service* tservice);
   void generate_service_interface (t_service* tservice);
   void generate_function_info     (t_service* tservice, t_function* tfunction);
 
@@ -118,19 +118,32 @@
 
   std::string function_signature(t_function* tfunction, std::string prefix="");
 
-
   std::string argument_list(t_struct* tstruct);
   std::string type_to_enum(t_type* ttype);
   std::string type_module(t_type* ttype);
 
-  std::string capitalize(std::string in) {
-    in[0] = toupper(in[0]);
-    return in;
+  std::string make_safe_for_module_name(std::string in) {
+    if(legacy_names_) {
+      return decapitalize(in);
+    } else {
+      return underscore(in);
+    }
   }
 
-  std::string make_safe_for_module_name(std::string in) {
-    in[0] = tolower(in[0]);
-    return in;
+  std::string atomify(std::string in) {
+    if(legacy_names_) {
+      return "'" + decapitalize(in) + "'";
+    } else {
+      return "'" + in + "'";
+    }
+  }
+
+  std::string constify(std::string in) {
+    if(legacy_names_) {
+      return capitalize(in);
+    } else {
+      return uppercase(in);
+    }
   }
 
   static std::string comment(string in);
@@ -139,6 +152,9 @@
 
   bool has_default_value(t_field *);
 
+  /* if true retain pre 0.9.2 naming scheme for functions, atoms and consts */
+  bool legacy_names_;
+
   /**
    * add function to export list
    */
@@ -199,33 +215,33 @@
   export_types_lines_first_ = true;
 
   // types files
-  string f_types_name = get_out_dir()+program_name_+"_types.erl";
-  string f_types_hrl_name = get_out_dir()+program_name_+"_types.hrl";
+  string f_types_name = get_out_dir() + make_safe_for_module_name(program_name_) + "_types.erl";
+  string f_types_hrl_name = get_out_dir() + make_safe_for_module_name(program_name_) + "_types.hrl";
 
   f_types_file_.open(f_types_name.c_str());
   f_types_hrl_file_.open(f_types_hrl_name.c_str());
 
-  hrl_header(f_types_hrl_file_, program_name_ + "_types");
+  hrl_header(f_types_hrl_file_, make_safe_for_module_name(program_name_) + "_types");
 
   f_types_file_ <<
     erl_autogen_comment() << endl <<
-    "-module(" << program_name_ << "_types)." << endl <<
+    "-module(" << make_safe_for_module_name(program_name_) << "_types)." << endl <<
     erl_imports() << endl;
 
   f_types_file_ <<
-    "-include(\"" << program_name_ << "_types.hrl\")." << endl <<
+    "-include(\"" << make_safe_for_module_name(program_name_) << "_types.hrl\")." << endl <<
     endl;
 
   f_types_hrl_file_ << render_includes() << endl;
 
   // consts file
-  string f_consts_name = get_out_dir()+program_name_+"_constants.hrl";
+  string f_consts_name = get_out_dir() + make_safe_for_module_name(program_name_) + "_constants.hrl";
   f_consts_.open(f_consts_name.c_str());
 
   f_consts_ <<
     erl_autogen_comment() << endl <<
     erl_imports() << endl <<
-    "-include(\"" << program_name_ << "_types.hrl\")." << endl <<
+    "-include(\"" << make_safe_for_module_name(program_name_) << "_types.hrl\")." << endl <<
     endl;
 }
 
@@ -334,9 +350,14 @@
 
   for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
     int value = (*c_iter)->get_value();
-    string name = capitalize((*c_iter)->get_name());
+    string name = (*c_iter)->get_name();
     indent(f_types_hrl_file_) <<
-      "-define(" << program_name_ << "_" << tenum->get_name() << "_" << name << ", " << value << ")."<< endl;
+      "-define(" <<
+      constify(make_safe_for_module_name(program_name_)) <<
+      "_" << constify(tenum->get_name()) <<
+      "_" << constify(name) <<
+      ", " << value << ")." <<
+    endl;
   }
 
   f_types_hrl_file_ << endl;
@@ -347,10 +368,15 @@
  */
 void t_erl_generator::generate_const(t_const* tconst) {
   t_type* type = tconst->get_type();
-  string name = capitalize(tconst->get_name());
+  string name = tconst->get_name();
   t_const_value* value = tconst->get_value();
 
-  f_consts_ << "-define(" << program_name_ << "_" << name << ", " << render_const_value(type, value) << ")." << endl << endl;
+  f_consts_ <<
+    "-define(" << constify(make_safe_for_module_name(program_name_)) <<
+    "_" << constify(name) <<
+    ", " << render_const_value(type, value) <<
+    ")." << endl <<
+  endl;
 }
 
 /**
@@ -391,7 +417,7 @@
     indent(out) << value->get_integer();
 
   } else if (type->is_struct() || type->is_xception()) {
-    out << "#'" << type->get_name() << "'{";
+    out << "#" << atomify(type->get_name()) << "{";
     const vector<t_field*>& fields = ((t_struct*)type)->get_members();
     vector<t_field*>::const_iterator f_iter;
     const map<t_const_value*, t_const_value*>& val = value->get_map();
@@ -474,7 +500,7 @@
 string t_erl_generator::render_default_value(t_field* field) {
   t_type *type = field->get_type();
   if (type->is_struct() || type->is_xception()) {
-    return "#'" + type->get_name() + "'{}";
+    return "#" + atomify(type->get_name()) + "{}";
   } else if (type->is_map()) {
     return "dict:new()";
   } else if (type->is_set()) {
@@ -508,7 +534,7 @@
   } else if (type->is_enum()) {
     return "integer()";
   } else if (type->is_struct() || type->is_xception()) {
-    return "'" + type->get_name() + "'()";
+    return atomify(type->get_name()) + "()";
   } else if (type->is_map()) {
     return "dict()";
   } else if (type->is_set()) {
@@ -579,7 +605,7 @@
 
   out << buf.str() << endl;
   out <<
-    "-type "+type_name (tstruct) << "() :: #" + type_name (tstruct) + "{}."
+    "-type " + type_name(tstruct) << "() :: #" + type_name(tstruct) + "{}."
       << endl << endl;
 }
 
@@ -589,7 +615,7 @@
 
 void t_erl_generator::generate_erl_struct_member(ostream & out, t_field * tmember)
 {
-  out << "'" << tmember->get_name() << "'";
+  out << atomify(tmember->get_name());
   if (has_default_value(tmember))
     out << " = "  << render_member_value(tmember);
   out << " :: " << render_member_type(tmember);
@@ -648,12 +674,10 @@
  * @param tservice The service definition
  */
 void t_erl_generator::generate_service(t_service* tservice) {
-  // somehow this point is reached before the constructor and it's not downcased yet
-  // ...awesome
-  service_name_[0] = tolower(service_name_[0]);
+  service_name_ = make_safe_for_module_name(service_name_);
 
-  string f_service_hrl_name = get_out_dir()+service_name_+"_thrift.hrl";
-  string f_service_name = get_out_dir()+service_name_+"_thrift.erl";
+  string f_service_hrl_name = get_out_dir() + service_name_ + "_thrift.hrl";
+  string f_service_name = get_out_dir() + service_name_ + "_thrift.erl";
   f_service_file_.open(f_service_name.c_str());
   f_service_hrl_.open(f_service_hrl_name.c_str());
 
@@ -670,7 +694,7 @@
   }
 
   f_service_hrl_ <<
-    "-include(\"" << program_name_ << "_types.hrl\")." << endl <<
+    "-include(\"" << make_safe_for_module_name(program_name_) << "_types.hrl\")." << endl <<
     endl;
 
   // Generate the three main parts of the service (well, two for now in PHP)
@@ -763,7 +787,6 @@
   indent(f_service_) << endl;
 }
 
-
 /**
  * Generates a function_info(FunctionName, params_type) and
  * function_info(FunctionName, reply_type)
@@ -771,9 +794,7 @@
 void t_erl_generator::generate_function_info(t_service* tservice,
                                                 t_function* tfunction) {
   (void) tservice;
-  string name_atom = "'" + tfunction->get_name() + "'";
-
-
+  string name_atom = atomify(tfunction->get_name());
 
   t_struct* xs = tfunction->get_xceptions();
   t_struct* arg_struct = tfunction->get_arglist();
@@ -888,10 +909,10 @@
   string name = ttype->get_name();
 
   if (ttype->is_struct() || ttype->is_xception() || ttype->is_service()) {
-    name = "'" + ttype->get_name() + "'";
+    name = ttype->get_name();
   }
 
-  return prefix + name;
+  return atomify(prefix + name);
 }
 
 /**
@@ -987,7 +1008,13 @@
           string  name         = member->get_name();
           string  value        = render_member_value(member);
           string  requiredness = render_member_requiredness(member);
-          buf << "{" << key << ", "  << requiredness << ", "  << type << ", '" << name << "'"<< ", "  << value << "}";
+          buf <<
+            "{" << key <<
+            ", "  << requiredness <<
+            ", "  << type <<
+            ", " << atomify(name) <<
+            ", "  << value <<
+          "}";
         }
 
         if ( ++i != end ) {
@@ -998,7 +1025,7 @@
       buf << "]}" << endl;
       return buf.str();
     } else {
-      return "{struct, {'" + type_module(type) + "', " + type_name(type) + "}}";
+      return "{struct, {" + atomify(type_module(type)) + ", " + type_name(type) + "}}";
     }
   } else if (type->is_map()) {
     // {map, KeyType, ValType}
@@ -1026,5 +1053,7 @@
   return make_safe_for_module_name(ttype->get_program()->get_name()) + "_types";
 }
 
-THRIFT_REGISTER_GENERATOR(erl, "Erlang", "")
+THRIFT_REGISTER_GENERATOR(erl, "Erlang",
+"    legacynames: Output files retain naming conventions of Thrift 0.9.1 and earlier.\n"
+)
 
diff --git a/compiler/cpp/src/generate/t_generator.h b/compiler/cpp/src/generate/t_generator.h
index d131777..04d88d9 100644
--- a/compiler/cpp/src/generate/t_generator.h
+++ b/compiler/cpp/src/generate/t_generator.h
@@ -199,6 +199,12 @@
     }
     return in;
   }
+  static std::string uppercase(std::string in) {
+    for (size_t i = 0; i < in.size(); ++i) {
+      in[i] = toupper(in[i]);
+    }
+    return in;
+  }
   /**
    * Transforms a camel case string to an equivalent one separated by underscores
    * e.g. aMultiWord -> a_multi_word
diff --git a/contrib/Vagrantfile b/contrib/Vagrantfile
index c99719c..67e5622 100644
--- a/contrib/Vagrantfile
+++ b/contrib/Vagrantfile
@@ -101,7 +101,7 @@
 echo "Starting Apache Thrift build..."
 cd /thrift
 sh bootstrap.sh
-sh configure --without-erlang
+sh configure
 make
 make check
 echo "Finished building Apache Thrift."
diff --git a/lib/erl/test/test_client.erl b/lib/erl/test/test_client.erl
index 79708ca..db4d2d1 100644
--- a/lib/erl/test/test_client.erl
+++ b/lib/erl/test/test_client.erl
@@ -21,7 +21,7 @@
 
 -export([start/0, start/1]).
 
--include("gen-erl/thriftTest_types.hrl").
+-include("gen-erl/thrift_test_types.hrl").
 
 -record(options, {port = 9090,
                   client_opts = []}).
@@ -50,15 +50,15 @@
 start(Args) ->
   #options{port = Port, client_opts = ClientOpts} = parse_args(Args),
   {ok, Client0} = thrift_client_util:new(
-    "127.0.0.1", Port, thriftTest_thrift, ClientOpts),
+    "127.0.0.1", Port, thrift_test_thrift, ClientOpts),
 
-  DemoXtruct = #xtruct{
+  DemoXtruct = #'Xtruct'{
     string_thing = <<"Zero">>,
     byte_thing = 1,
     i32_thing = 9128361,
     i64_thing = 9223372036854775807},
 
-  DemoNest = #xtruct2{
+  DemoNest = #'Xtruct2'{
     byte_thing = 7,
     struct_thing = DemoXtruct,
     % Note that we don't set i32_thing, it will come back as undefined
@@ -88,7 +88,7 @@
   {Client11, {ok, DemoDict}}        = thrift_client:call(Client10, testMap, [DemoDict]),
   {Client12, {ok, DemoSet}}         = thrift_client:call(Client11, testSet, [DemoSet]),
   {Client13, {ok, [-1,2,3]}}        = thrift_client:call(Client12, testList, [[-1,2,3]]),
-  {Client14, {ok, 1}}               = thrift_client:call(Client13, testEnum, [?thriftTest_Numberz_ONE]),
+  {Client14, {ok, 1}}               = thrift_client:call(Client13, testEnum, [?THRIFT_TEST_NUMBERZ_ONE]),
   {Client15, {ok, 309858235082523}} = thrift_client:call(Client14, testTypedef, [309858235082523]),
 
   % No python implementation, but works with C++ and Erlang.
@@ -96,7 +96,7 @@
   %io:format("~p~n", [InsaneResult]),
   Client16 = Client15,
 
-  {Client17, {ok, #xtruct{string_thing = <<"Message">>}}} =
+  {Client17, {ok, #'Xtruct'{string_thing = <<"Message">>}}} =
     thrift_client:call(Client16, testMultiException, ["Safe", "Message"]),
 
   Client18 =
@@ -105,10 +105,10 @@
       io:format("Unexpected return! ~p~n", [Result1]),
       ClientS1
     catch
-      throw:{ClientS2, {exception, ExnS1 = #xception{}}} ->
-        #xception{errorCode = 1001, message = <<"This is an Xception">>} = ExnS1,
+      throw:{ClientS2, {exception, ExnS1 = #'Xception'{}}} ->
+        #'Xception'{errorCode = 1001, message = <<"This is an Xception">>} = ExnS1,
         ClientS2;
-      throw:{ClientS2, {exception, _ExnS1 = #xception2{}}} ->
+      throw:{ClientS2, {exception, _ExnS1 = #'Xception2'{}}} ->
         io:format("Wrong exception type!~n", []),
         ClientS2
     end,
@@ -119,12 +119,12 @@
       io:format("Unexpected return! ~p~n", [Result2]),
       ClientS3
     catch
-      throw:{ClientS4, {exception, _ExnS2 = #xception{}}} ->
+      throw:{ClientS4, {exception, _ExnS2 = #'Xception'{}}} ->
         io:format("Wrong exception type!~n", []),
         ClientS4;
-      throw:{ClientS4, {exception, ExnS2 = #xception2{}}} ->
-        #xception2{errorCode = 2002,
-                   struct_thing = #xtruct{
+      throw:{ClientS4, {exception, ExnS2 = #'Xception2'{}}} ->
+        #'Xception2'{errorCode = 2002,
+                   struct_thing = #'Xtruct'{
                      string_thing = <<"This is an Xception2">>}} = ExnS2,
         ClientS4
     end,
diff --git a/lib/erl/test/test_disklog.erl b/lib/erl/test/test_disklog.erl
index d8cd4fc..6286bc0 100644
--- a/lib/erl/test/test_disklog.erl
+++ b/lib/erl/test/test_disklog.erl
@@ -31,7 +31,7 @@
   {ok, ProtocolFactory} =
     thrift_binary_protocol:new_protocol_factory( TransportFactory, []),
   {ok, Proto} = ProtocolFactory(),
-  {ok, Client0} = thrift_client:new(Proto, thriftTest_thrift),
+  {ok, Client0} = thrift_client:new(Proto, thrift_test_thrift),
 
   io:format("Client started~n"),
 
@@ -70,7 +70,7 @@
   {ok, ProtocolFactory} =
     thrift_binary_protocol:new_protocol_factory(BufFactory, []),
   {ok, Proto} = ProtocolFactory(),
-  {ok, Client0} = thrift_client:new(Proto, thriftTest_thrift),
+  {ok, Client0} = thrift_client:new(Proto, thrift_test_thrift),
 
   io:format("Client started~n"),
 
diff --git a/lib/erl/test/test_membuffer.erl b/lib/erl/test/test_membuffer.erl
index 8892df0..671ae11 100644
--- a/lib/erl/test/test_membuffer.erl
+++ b/lib/erl/test/test_membuffer.erl
@@ -22,10 +22,10 @@
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
 
--include("gen-erl/thriftTest_types.hrl").
+-include("gen-erl/thrift_test_types.hrl").
 
 test_data() ->
-  #xtruct {
+  #'Xtruct'{
     string_thing = <<"foobar">>,
     byte_thing = 123,
     i32_thing = 1234567,
@@ -37,11 +37,11 @@
   {ok, Protocol0} = thrift_binary_protocol:new(Transport),
   TestData = test_data(),
   {Protocol1, ok} = thrift_protocol:write(Protocol0,
-    {{struct, element(2, thriftTest_types:struct_info('xtruct'))},
+    {{struct, element(2, thrift_test_types:struct_info('Xtruct'))},
       TestData}),
   {_Protocol2, {ok, Result}} = thrift_protocol:read(Protocol1,
-    {struct, element(2, thriftTest_types:struct_info('xtruct'))},
-    'xtruct'),
+    {struct, element(2, thrift_test_types:struct_info('Xtruct'))},
+    'Xtruct'),
   Result = TestData.
 
 encode_decode_2_test() ->
@@ -49,43 +49,43 @@
   {ok, Protocol0} = thrift_binary_protocol:new(Transport),
   TestData = test_data(),
   {Protocol1, ok} = thrift_protocol:write(Protocol0,
-    {{struct, element(2, thriftTest_types:struct_info('xtruct'))},
+    {{struct, element(2, thrift_test_types:struct_info('Xtruct'))},
       TestData}),
   {_Protocol2, {ok, Result}} = thrift_protocol:read(Protocol1,
-    {struct, element(2, thriftTest_types:struct_info('xtruct3'))},
-    'xtruct3'),
+    {struct, element(2, thrift_test_types:struct_info('Xtruct3'))},
+    'Xtruct3'),
 
-  Result = #xtruct3{string_thing = TestData#xtruct.string_thing,
+  Result = #'Xtruct3'{string_thing = TestData#'Xtruct'.string_thing,
     changed = undefined,
-    i32_thing = TestData#xtruct.i32_thing,
-    i64_thing = TestData#xtruct.i64_thing}.
+    i32_thing = TestData#'Xtruct'.i32_thing,
+    i64_thing = TestData#'Xtruct'.i64_thing}.
 
 
 encode_decode_3_test() ->
   {ok, Transport} = thrift_memory_buffer:new(),
   {ok, Protocol0} = thrift_binary_protocol:new(Transport),
-  TestData = #bools{im_true = true, im_false = false},
+  TestData = #'Bools'{im_true = true, im_false = false},
   {Protocol1, ok} = thrift_protocol:write(Protocol0,
-    {{struct, element(2, thriftTest_types:struct_info('bools'))},
+    {{struct, element(2, thrift_test_types:struct_info('Bools'))},
       TestData}),
   {_Protocol2, {ok, Result}} = thrift_protocol:read(Protocol1,
-    {struct, element(2, thriftTest_types:struct_info('bools'))},
-    'bools'),
+    {struct, element(2, thrift_test_types:struct_info('Bools'))},
+    'Bools'),
 
-  true = TestData#bools.im_true  =:= Result#bools.im_true,
-  true = TestData#bools.im_false =:= Result#bools.im_false.
+  true = TestData#'Bools'.im_true  =:= Result#'Bools'.im_true,
+  true = TestData#'Bools'.im_false =:= Result#'Bools'.im_false.
 
 
 encode_decode_4_test() ->
   {ok, Transport} = thrift_memory_buffer:new(),
   {ok, Protocol0} = thrift_binary_protocol:new(Transport),
-  TestData = #insanity{xtructs=[]},
+  TestData = #'Insanity'{xtructs=[]},
   {Protocol1, ok} = thrift_protocol:write(Protocol0,
-    {{struct, element(2, thriftTest_types:struct_info('insanity'))},
+    {{struct, element(2, thrift_test_types:struct_info('Insanity'))},
       TestData}),
   {_Protocol2, {ok, Result}} = thrift_protocol:read(Protocol1,
-    {struct, element(2, thriftTest_types:struct_info('insanity'))},
-    'insanity'),
+    {struct, element(2, thrift_test_types:struct_info('Insanity'))},
+    'Insanity'),
 
   TestData = Result.
 
@@ -98,17 +98,17 @@
   {ok, Protocol0} = thrift_binary_protocol:new(Transport0),
   TestData = test_data(),
   {Protocol1, ok} = thrift_protocol:write(Protocol0,
-    {{struct, element(2, thriftTest_types:struct_info('xtruct'))},
+    {{struct, element(2, thrift_test_types:struct_info('Xtruct'))},
       TestData}),
   % flush now returns the buffer
-  {_Protocol2, Buf} = thrift_protocol:flush_transport (Protocol1),
+  {_Protocol2, Buf} = thrift_protocol:flush_transport(Protocol1),
 
   % now the reading part
   {ok, T2} = thrift_memory_buffer:new (Buf),
   {ok, P2} = thrift_binary_protocol:new(T2),
   {_, {ok, Result}} = thrift_protocol:read(P2,
-    {struct, element(2, thriftTest_types:struct_info('xtruct'))},
-    'xtruct'),
+    {struct, element(2, thrift_test_types:struct_info('Xtruct'))},
+    'Xtruct'),
 
   Result = TestData.
 
diff --git a/lib/erl/test/test_server.erl b/lib/erl/test/test_server.erl
index 8cabd6f..a4145d6 100644
--- a/lib/erl/test/test_server.erl
+++ b/lib/erl/test/test_server.erl
@@ -21,7 +21,7 @@
 
 -export([go/0, go/1, start_link/2, handle_function/2]).
 
--include("gen-erl/thriftTest_types.hrl").
+-include("gen-erl/thrift_test_types.hrl").
 
 -record(options, {port = 9090,
                   server_opts = []}).
@@ -82,7 +82,7 @@
     {reply, Double};
 
 handle_function(testStruct,
-                {Struct = #xtruct{string_thing = String,
+                {Struct = #'Xtruct'{string_thing = String,
                                  byte_thing = Byte,
                                  i32_thing = I32,
                                  i64_thing = I64}})
@@ -94,8 +94,8 @@
     {reply, Struct};
 
 handle_function(testNest,
-                {Nest}) when is_record(Nest, xtruct2),
-                             is_record(Nest#xtruct2.struct_thing, xtruct) ->
+                {Nest}) when is_record(Nest, 'Xtruct2'),
+                             is_record(Nest#'Xtruct2'.struct_thing, 'Xtruct') ->
     io:format("testNest: ~p~n", [Nest]),
     {reply, Nest};
 
@@ -130,30 +130,30 @@
                              {-4, dict:from_list(NegList)}]),
     {reply, MapMap};
 
-handle_function(testInsanity, {Insanity}) when is_record(Insanity, insanity) ->
-    Hello = #xtruct{string_thing = <<"Hello2">>,
+handle_function(testInsanity, {Insanity}) when is_record(Insanity, 'Insanity') ->
+    Hello = #'Xtruct'{string_thing = <<"Hello2">>,
                     byte_thing = 2,
                     i32_thing = 2,
                     i64_thing = 2},
 
-    Goodbye = #xtruct{string_thing = <<"Goodbye4">>,
+    Goodbye = #'Xtruct'{string_thing = <<"Goodbye4">>,
                       byte_thing = 4,
                       i32_thing = 4,
                       i64_thing = 4},
-    Crazy = #insanity{
-      userMap = dict:from_list([{?thriftTest_Numberz_EIGHT, 8}]),
+    Crazy = #'Insanity'{
+      userMap = dict:from_list([{?THRIFT_TEST_NUMBERZ_EIGHT, 8}]),
       xtructs = [Goodbye]
       },
 
-    Looney = #insanity{
-      userMap = dict:from_list([{?thriftTest_Numberz_FIVE, 5}]),
+    Looney = #'Insanity'{
+      userMap = dict:from_list([{?THRIFT_TEST_NUMBERZ_FIVE, 5}]),
       xtructs = [Hello]
       },
 
-    FirstMap = dict:from_list([{?thriftTest_Numberz_TWO, Crazy},
-                               {?thriftTest_Numberz_THREE, Crazy}]),
+    FirstMap = dict:from_list([{?THRIFT_TEST_NUMBERZ_TWO, Crazy},
+                               {?THRIFT_TEST_NUMBERZ_THREE, Crazy}]),
 
-    SecondMap = dict:from_list([{?thriftTest_Numberz_SIX, Looney}]),
+    SecondMap = dict:from_list([{?THRIFT_TEST_NUMBERZ_SIX, Looney}]),
 
     Insane = dict:from_list([{1, FirstMap},
                              {2, SecondMap}]),
@@ -170,7 +170,7 @@
        is_integer(Arg5) ->
 
     io:format("testMulti(~p)~n", [Args]),
-    {reply, #xtruct{string_thing = <<"Hello2">>,
+    {reply, #'Xtruct'{string_thing = <<"Hello2">>,
                     byte_thing = Arg0,
                     i32_thing = Arg1,
                     i64_thing = Arg2}};
@@ -179,7 +179,7 @@
     io:format("testException(~p)~n", [String]),
     case String of
         <<"Xception">> ->
-            throw(#xception{errorCode = 1001,
+            throw(#'Xception'{errorCode = 1001,
                             message = String});
         _ ->
             ok
@@ -189,14 +189,14 @@
     io:format("testMultiException(~p, ~p)~n", [Arg0, Arg1]),
     case Arg0 of
         <<"Xception">> ->
-            throw(#xception{errorCode = 1001,
+            throw(#'Xception'{errorCode = 1001,
                                    message = <<"This is an Xception">>});
         <<"Xception2">> ->
-            throw(#xception2{errorCode = 2002,
+            throw(#'Xception2'{errorCode = 2002,
                                     struct_thing =
-                                    #xtruct{string_thing = <<"This is an Xception2">>}});
+                                    #'Xtruct'{string_thing = <<"This is an Xception2">>}});
         _ ->
-            {reply, #xtruct{string_thing = Arg1}}
+            {reply, #'Xtruct'{string_thing = Arg1}}
     end;
 
 handle_function(testOneway, {Seconds}) ->
diff --git a/lib/erl/test/test_thrift_1151.erl b/lib/erl/test/test_thrift_1151.erl
index 8b1d937..f4a910e 100644
--- a/lib/erl/test/test_thrift_1151.erl
+++ b/lib/erl/test/test_thrift_1151.erl
@@ -6,19 +6,29 @@
 -include_lib("eunit/include/eunit.hrl").
 
 unmatched_struct_test() ->
-  S1 = #structC{x=#structB{x=1}},
+  S1 = #'StructC'{x=#'StructB'{x=1}},
   {ok, Transport} = thrift_memory_buffer:new(),
   {ok, Protocol} = thrift_binary_protocol:new(Transport),
-  ?assertException (error, struct_unmatched,
-    thrift_protocol:write(Protocol,
-      {{struct, element(2, thrift1151_types:struct_info('structC'))}, S1})).
+  ?assertException(
+    error,
+    struct_unmatched,
+    thrift_protocol:write(
+      Protocol,
+      {{struct, element(2, thrift1151_types:struct_info('StructC'))}, S1}
+    )
+  ).
 
 badarg_test() ->
-  S2 = #structC{x=#structA{x="1"}},
+  S2 = #'StructC'{x=#'StructA'{x="1"}},
   {ok, Transport} = thrift_memory_buffer:new(),
   {ok, Protocol} = thrift_binary_protocol:new(Transport),
-  ?assertException (error, badarg,
-    thrift_protocol:write(Protocol,
-      {{struct, element(2, thrift1151_types:struct_info('structC'))}, S2})).
+  ?assertException(
+    error,
+    badarg,
+    thrift_protocol:write(
+      Protocol,
+      {{struct, element(2, thrift1151_types:struct_info('StructC'))}, S2}
+    )
+  ).
 
 -endif.
diff --git a/test/erl/LegacyNames.thrift b/test/erl/LegacyNames.thrift
new file mode 100644
index 0000000..38f2729
--- /dev/null
+++ b/test/erl/LegacyNames.thrift
@@ -0,0 +1,33 @@
+enum Numberz
+{
+  ONE = 1,
+  TWO,
+  THREE,
+  FIVE = 5,
+  SIX,
+  EIGHT = 8
+}
+
+const Numberz myNumberz = Numberz.ONE;
+
+struct CapitalizedStruct
+{
+  1: i32 Id,
+  2: binary message
+}
+
+struct ListCapitalizedStructs
+{
+  1: list<CapitalizedStruct> structs
+}
+
+exception Xception {
+  1: i32 errorCode,
+  2: binary message
+}
+
+service LegacyNames
+{
+  ListCapitalizedStructs Names(1: CapitalizedStruct foo, 2: CapitalizedStruct bar)
+    throws(1: Xception err)
+}
\ No newline at end of file
diff --git a/test/erl/Makefile.am b/test/erl/Makefile.am
index 911fceb..309d07e 100644
--- a/test/erl/Makefile.am
+++ b/test/erl/Makefile.am
@@ -26,6 +26,7 @@
 	for f in $(THRIFT_FILES) ; do \
 	  $(THRIFT) --gen erl $$f ; \
 	done ; \
+	$(THRIFT) --gen erl:legacynames LegacyNames.thrift
 	touch .generated
 
 check: .generated
diff --git a/test/erl/src/legacy_names_test.erl b/test/erl/src/legacy_names_test.erl
new file mode 100644
index 0000000..2ace7d0
--- /dev/null
+++ b/test/erl/src/legacy_names_test.erl
@@ -0,0 +1,69 @@
+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+-module(legacy_names_test).
+-compile(export_all).
+
+-include_lib("eunit/include/eunit.hrl").
+
+-include("legacyNames_constants.hrl").
+
+record_generation_test_() ->
+  [
+    {"capitalizedStruct record", ?_assertMatch(
+      {capitalizedStruct, _, _},
+      #capitalizedStruct{id=null,message=null}
+    )}
+  ].
+
+struct_info_test_() ->
+  [
+    {"capitalizedStruct extended definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, i32, 'id', undefined},
+        {2, undefined, string, 'message', undefined}
+      ]},
+      legacyNames_types:struct_info_ext(capitalizedStruct)
+    )},
+    {"listCapitalizedStructs extended definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, {struct, {'legacyNames_types', 'capitalizedStruct'}}}, 'structs', []}
+      ]},
+      legacyNames_types:struct_info_ext(listCapitalizedStructs)
+    )}
+  ].
+
+service_info_test_() ->
+  [
+    {"names params", ?_assertEqual(
+      {struct, [
+        {1, {struct, {'legacyNames_types', 'capitalizedStruct'}}},
+        {2, {struct, {'legacyNames_types', 'capitalizedStruct'}}}
+      ]},
+      legacyNames_thrift:function_info(names, params_type)
+    )},
+    {"names reply", ?_assertEqual(
+      {struct, {'legacyNames_types', 'listCapitalizedStructs'}},
+      legacyNames_thrift:function_info(names, reply_type)
+    )},
+    {"names exceptions", ?_assertEqual(
+      {struct, [{1, {struct, {'legacyNames_types', 'xception'}}}]},
+      legacyNames_thrift:function_info(names, exceptions)
+    )}
+  ].
\ No newline at end of file
diff --git a/test/erl/src/nameConflictTest_test.erl b/test/erl/src/name_conflict_test.erl
similarity index 76%
rename from test/erl/src/nameConflictTest_test.erl
rename to test/erl/src/name_conflict_test.erl
index 7ce4886..40c8204 100644
--- a/test/erl/src/nameConflictTest_test.erl
+++ b/test/erl/src/name_conflict_test.erl
@@ -17,12 +17,12 @@
 %% under the License.
 %%
 
--module(nameConflictTest_test).
+-module(name_conflict_test).
 -compile(export_all).
 
 -include_lib("eunit/include/eunit.hrl").
 
--include("nameConflictTest_constants.hrl").
+-include("name_conflict_test_constants.hrl").
 
 record_generation_test_() ->
   [
@@ -116,85 +116,85 @@
   [
     {"using definition", ?_assertEqual(
       {struct, [{1, double},{2, double}]},
-      nameConflictTest_types:struct_info(using)
+      name_conflict_test_types:struct_info(using)
     )},
     {"delegate definition", ?_assertEqual(
       {struct, [
         {1, string},
-        {2, {struct, {nameConflictTest_types, delegate}}}
+        {2, {struct, {name_conflict_test_types, delegate}}}
       ]},
-      nameConflictTest_types:struct_info(delegate)
+      name_conflict_test_types:struct_info(delegate)
     )},
     {"get definition", ?_assertEqual(
       {struct, [{1, bool}]},
-      nameConflictTest_types:struct_info(get)
+      name_conflict_test_types:struct_info(get)
     )},
     {"partial definition", ?_assertEqual(
-      {struct, [{1, {struct, {nameConflictTest_types, using}}}]},
-      nameConflictTest_types:struct_info(partial)
+      {struct, [{1, {struct, {name_conflict_test_types, using}}}]},
+      name_conflict_test_types:struct_info(partial)
     )},
     {"ClassAndProp definition", ?_assertEqual(
       {struct, [{1, bool},{2, bool},{3, bool},{4, bool}]},
-      nameConflictTest_types:struct_info('ClassAndProp')
+      name_conflict_test_types:struct_info('ClassAndProp')
     )},
     {"second_chance definition", ?_assertEqual(
       {struct, [{1, bool},{2, bool},{3, bool},{4, bool}]},
-      nameConflictTest_types:struct_info(second_chance)
+      name_conflict_test_types:struct_info(second_chance)
     )},
     {"NOW_EAT_THIS definition", ?_assertEqual(
       {struct, [{1, bool},{2, bool},{3, bool},{4, bool}]},
-      nameConflictTest_types:struct_info('NOW_EAT_THIS')
+      name_conflict_test_types:struct_info('NOW_EAT_THIS')
     )},
     {"TheEdgeCase definition", ?_assertEqual(
       {struct, [{1, bool},{2, bool},{3, bool},{4, bool},{5, bool},{6, bool}]},
-      nameConflictTest_types:struct_info('TheEdgeCase')
+      name_conflict_test_types:struct_info('TheEdgeCase')
     )},
     {"theEdgeCase definition", ?_assertEqual(
       {struct, [{1, bool},{2, bool},{3, bool},{4, bool},{5, bool},{6, bool}]},
-      nameConflictTest_types:struct_info(theEdgeCase)
+      name_conflict_test_types:struct_info(theEdgeCase)
     )},
     {"Tricky_ definition", ?_assertEqual(
       {struct, [{1, bool},{2, bool}]},
-      nameConflictTest_types:struct_info('Tricky_')
+      name_conflict_test_types:struct_info('Tricky_')
     )},
     {"Nested definition", ?_assertEqual(
       {struct, [
-        {1, {struct, {nameConflictTest_types, 'ClassAndProp'}}},
-        {2, {struct, {nameConflictTest_types, second_chance}}},
-        {3, {struct, {nameConflictTest_types, 'NOW_EAT_THIS'}}},
-        {4, {struct, {nameConflictTest_types, 'TheEdgeCase'}}},
-        {5, {struct, {nameConflictTest_types, 'Tricky_'}}},
-        {6, {struct, {nameConflictTest_types, 'Nested'}}}
+        {1, {struct, {name_conflict_test_types, 'ClassAndProp'}}},
+        {2, {struct, {name_conflict_test_types, second_chance}}},
+        {3, {struct, {name_conflict_test_types, 'NOW_EAT_THIS'}}},
+        {4, {struct, {name_conflict_test_types, 'TheEdgeCase'}}},
+        {5, {struct, {name_conflict_test_types, 'Tricky_'}}},
+        {6, {struct, {name_conflict_test_types, 'Nested'}}}
       ]},
-      nameConflictTest_types:struct_info('Nested')
+      name_conflict_test_types:struct_info('Nested')
     )},
     {"Problem_ definition", ?_assertEqual(
       {struct, [{1, bool},{2, bool}]},
-      nameConflictTest_types:struct_info('Problem_')
+      name_conflict_test_types:struct_info('Problem_')
     )},
     {"using extended definition", ?_assertEqual(
       {struct, [
         {1, undefined, double, single, undefined},
         {2, undefined, double, integer, undefined}
       ]},
-      nameConflictTest_types:struct_info_ext(using)
+      name_conflict_test_types:struct_info_ext(using)
     )},
     {"delegate extended definition", ?_assertEqual(
       {struct, [
         {1, undefined, string, partial, undefined},
-        {2, undefined, {struct, {nameConflictTest_types, delegate}}, delegate, undefined}
+        {2, undefined, {struct, {name_conflict_test_types, delegate}}, delegate, undefined}
       ]},
-      nameConflictTest_types:struct_info_ext(delegate)
+      name_conflict_test_types:struct_info_ext(delegate)
     )},
     {"get extended definition", ?_assertEqual(
       {struct, [{1, undefined, bool, sbyte, undefined}]},
-      nameConflictTest_types:struct_info_ext(get)
+      name_conflict_test_types:struct_info_ext(get)
     )},
     {"partial extended definition", ?_assertEqual(
       {struct, [
-        {1, undefined, {struct, {nameConflictTest_types, using}}, using, #using{}}
+        {1, undefined, {struct, {name_conflict_test_types, using}}, using, #using{}}
       ]},
-      nameConflictTest_types:struct_info_ext(partial)
+      name_conflict_test_types:struct_info_ext(partial)
     )},
     {"ClassAndProp extended definition", ?_assertEqual(
       {struct, [
@@ -203,7 +203,7 @@
         {3, undefined, bool, 'ClassAndProp__', undefined},
         {4, undefined, bool, 'ClassAndProper', undefined}
       ]},
-      nameConflictTest_types:struct_info_ext('ClassAndProp')
+      name_conflict_test_types:struct_info_ext('ClassAndProp')
     )},
     {"second_chance extended definition", ?_assertEqual(
       {struct, [
@@ -212,7 +212,7 @@
         {3, undefined, bool, 'SECOND_CHANCE__', undefined},
         {4, undefined, bool, 'SECOND_CHANCES', undefined}
       ]},
-      nameConflictTest_types:struct_info_ext(second_chance)
+      name_conflict_test_types:struct_info_ext(second_chance)
     )},
     {"NOW_EAT_THIS extended definition", ?_assertEqual(
       {struct, [
@@ -221,7 +221,7 @@
         {3, undefined, bool, now_eat_this__, undefined},
         {4, undefined, bool, now_eat_this_and_this, undefined}
       ]},
-      nameConflictTest_types:struct_info_ext('NOW_EAT_THIS')
+      name_conflict_test_types:struct_info_ext('NOW_EAT_THIS')
     )},
     {"TheEdgeCase extended definition", ?_assertEqual(
       {struct, [
@@ -232,7 +232,7 @@
         {5, undefined, bool, 'TheEdgeCase_', undefined},
         {6, undefined, bool, 'TheEdgeCase__', undefined}
       ]},
-      nameConflictTest_types:struct_info_ext('TheEdgeCase')
+      name_conflict_test_types:struct_info_ext('TheEdgeCase')
     )},
     {"TheEdgeCase extended definition", ?_assertEqual(
       {struct, [
@@ -243,61 +243,61 @@
         {5, undefined, bool, 'TheEdgeCase_', undefined},
         {6, undefined, bool, 'TheEdgeCase__', undefined}
       ]},
-      nameConflictTest_types:struct_info_ext(theEdgeCase)
+      name_conflict_test_types:struct_info_ext(theEdgeCase)
     )},
     {"Tricky_ extended definition", ?_assertEqual(
       {struct, [
         {1, undefined, bool, tricky, undefined},
         {2, undefined, bool, 'Tricky', undefined}
       ]},
-      nameConflictTest_types:struct_info_ext('Tricky_')
+      name_conflict_test_types:struct_info_ext('Tricky_')
     )},
     {"Nested extended definition", ?_assertEqual(
       {struct, [
         {1, undefined, {struct, {
-          nameConflictTest_types,
+          name_conflict_test_types,
           'ClassAndProp'
         }}, 'ClassAndProp', #'ClassAndProp'{}},
         {2, undefined, {struct, {
-          nameConflictTest_types,
+          name_conflict_test_types,
           second_chance
         }}, second_chance, #second_chance{}},
         {3, undefined, {struct, {
-          nameConflictTest_types,
+          name_conflict_test_types,
           'NOW_EAT_THIS'
         }}, 'NOW_EAT_THIS', #'NOW_EAT_THIS'{}},
         {4, undefined, {struct, {
-          nameConflictTest_types,
+          name_conflict_test_types,
           'TheEdgeCase'
         }}, 'TheEdgeCase', #'TheEdgeCase'{}},
         {5, undefined, {struct, {
-          nameConflictTest_types,
+          name_conflict_test_types,
           'Tricky_'
         }}, 'Tricky_', #'Tricky_'{}},
         {6, undefined, {struct, {
-          nameConflictTest_types,
+          name_conflict_test_types,
           'Nested'
         }}, 'Nested', undefined}
       ]},
-      nameConflictTest_types:struct_info_ext('Nested')
+      name_conflict_test_types:struct_info_ext('Nested')
     )},
     {"Problem_ extended definition", ?_assertEqual(
       {struct, [
         {1, undefined, bool, problem, undefined},
         {2, undefined, bool, 'Problem', undefined}
       ]},
-      nameConflictTest_types:struct_info_ext('Problem_')
+      name_conflict_test_types:struct_info_ext('Problem_')
     )}
   ].
 
 service_info_test_() ->
   [
     {"event params", ?_assertEqual(
-      {struct, [{1, {struct, {nameConflictTest_types, partial}}}]},
+      {struct, [{1, {struct, {name_conflict_test_types, partial}}}]},
       extern_thrift:function_info(event, params_type)
     )},
     {"event reply", ?_assertEqual(
-      {struct, {nameConflictTest_types, delegate}},
+      {struct, {name_conflict_test_types, delegate}},
       extern_thrift:function_info(event, reply_type)
     )},
     {"event exceptions", ?_assertEqual(
@@ -305,7 +305,7 @@
       extern_thrift:function_info(event, exceptions)
     )},
     {"Foo params", ?_assertEqual(
-      {struct, [{1, {struct, {nameConflictTest_types, 'Nested'}}}]},
+      {struct, [{1, {struct, {name_conflict_test_types, 'Nested'}}}]},
       extern_thrift:function_info('Foo', params_type)
     )},
     {"Foo reply", ?_assertEqual(
@@ -313,7 +313,7 @@
       extern_thrift:function_info('Foo', reply_type)
     )},
     {"Foo exceptions", ?_assertEqual(
-      {struct, [{1, {struct, {nameConflictTest_types, 'Problem_'}}}]},
+      {struct, [{1, {struct, {name_conflict_test_types, 'Problem_'}}}]},
       extern_thrift:function_info('Foo', exceptions)
     )}
   ].
\ No newline at end of file
diff --git a/test/erl/src/thrift_test.app.src b/test/erl/src/thrift_test.app.src
index dd04926..4dcd377 100644
--- a/test/erl/src/thrift_test.app.src
+++ b/test/erl/src/thrift_test.app.src
@@ -25,8 +25,7 @@
   {vsn, "1.0.0-dev"},
 
   % All modules used by the application.
-  {modules, [
-  ]},
+  {modules, [legacy_names_test, name_conflict_test, thrift_test_test]},
 
   % All of the registered names the application uses. This can be ignored.
   {registered, []},
diff --git a/test/erl/src/thrift_test_test.erl b/test/erl/src/thrift_test_test.erl
new file mode 100644
index 0000000..07dfe1c
--- /dev/null
+++ b/test/erl/src/thrift_test_test.erl
@@ -0,0 +1,655 @@
+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+
+% don't rename this thrift_test, it clobbers generated files
+-module(thrift_test_test).
+-compile(export_all).
+
+-include_lib("eunit/include/eunit.hrl").
+
+-include("thrift_test_constants.hrl").
+
+constant_test_() ->
+  [
+    {"myNumberz equals 1", ?_assertEqual(1, ?THRIFT_TEST_MYNUMBERZ)}
+  ].
+
+record_generation_test_() ->
+  [
+    {"Bonk record", ?_assertMatch(
+      {'Bonk', _, _},
+      #'Bonk'{message=null,type=null}
+    )},
+    {"Bools record", ?_assertMatch(
+      {'Bools', _, _},
+      #'Bools'{im_true=null,im_false=null}
+    )},
+    {"Xtruct record", ?_assertMatch(
+      {'Xtruct', _, _, _, _},
+      #'Xtruct'{string_thing=null,byte_thing=null,i32_thing=null,i64_thing=null}
+    )},
+    {"Xtruct2 record", ?_assertMatch(
+      {'Xtruct2', _, _, _},
+      #'Xtruct2'{byte_thing=null,struct_thing=null,i32_thing=null}
+    )},
+    {"Xtruct3 record", ?_assertMatch(
+      {'Xtruct3', _, _, _, _},
+      #'Xtruct3'{string_thing=null,changed=null,i32_thing=null,i64_thing=null}
+    )},
+    {"Insanity record", ?_assertMatch(
+      {'Insanity', _, _},
+      #'Insanity'{userMap=null,xtructs=null}
+    )},
+    {"CrazyNesting record", ?_assertMatch(
+      {'CrazyNesting', _, _, _, _},
+      #'CrazyNesting'{
+        string_field=null,
+        set_field=null,
+        list_field=null,
+        binary_field=null
+      }
+    )},
+    {"Xception record", ?_assertMatch(
+      {'Xception', _, _},
+      #'Xception'{errorCode=null,message=null}
+    )},
+    {"Xception2 record", ?_assertMatch(
+      {'Xception2', _, _},
+      #'Xception2'{errorCode=null,struct_thing=null}
+    )},
+    {"EmptyStruct record", ?_assertMatch({'EmptyStruct'}, #'EmptyStruct'{})},
+    {"OneField record", ?_assertMatch({'OneField', _}, #'OneField'{field=null})},
+    {"VersioningTestV1 record", ?_assertMatch(
+      {'VersioningTestV1', _, _, _},
+      #'VersioningTestV1'{begin_in_both=null,old_string=null,end_in_both=null}
+    )},
+    {"VersioningTestV2 record", ?_assertMatch(
+      {'VersioningTestV2', _, _, _, _, _, _, _, _, _, _, _, _},
+      #'VersioningTestV2'{
+        begin_in_both=null,
+        newint=null,
+        newbyte=null,
+        newshort=null,
+        newlong=null,
+        newdouble=null,
+        newstruct=null,
+        newlist=null,
+        newset=null,
+        newmap=null,
+        newstring=null,
+        end_in_both=null
+      }
+    )},
+    {"ListTypeVersioningV1 record", ?_assertMatch(
+      {'ListTypeVersioningV1', _, _},
+      #'ListTypeVersioningV1'{myints=null,hello=null}
+    )},
+    {"ListTypeVersioningV2 record", ?_assertMatch(
+      {'ListTypeVersioningV2', _, _},
+      #'ListTypeVersioningV2'{strings=null,hello=null}
+    )},
+    {"GuessProtocolStruct record", ?_assertMatch(
+      {'GuessProtocolStruct', _},
+      #'GuessProtocolStruct'{map_field=null}
+    )},
+    {"LargeDeltas record", ?_assertMatch(
+      {'LargeDeltas', _, _, _, _, _, _, _, _, _, _},
+      #'LargeDeltas'{
+        b1=null,
+        b10=null,
+        b100=null,
+        check_true=null,
+        b1000=null,
+        check_false=null,
+        vertwo2000=null,
+        a_set2500=null,
+        vertwo3000=null,
+        big_numbers=null
+      }
+    )},
+    {"NestedListsI32x2 record", ?_assertMatch(
+      {'NestedListsI32x2', _},
+      #'NestedListsI32x2'{integerlist=null}
+    )},
+    {"NestedListsI32x3 record", ?_assertMatch(
+      {'NestedListsI32x3', _},
+      #'NestedListsI32x3'{integerlist=null}
+    )},
+    {"NestedMixedx2 record", ?_assertMatch(
+      {'NestedMixedx2', _, _, _},
+      #'NestedMixedx2'{
+        int_set_list=null,
+        map_int_strset=null,
+        map_int_strset_list=null
+      }
+    )},
+    {"ListBonks record", ?_assertMatch({'ListBonks', _}, #'ListBonks'{bonk=null})},
+    {"NestedListsBonk record", ?_assertMatch(
+      {'NestedListsBonk', _},
+      #'NestedListsBonk'{bonk=null}
+    )},
+    {"BoolTest record", ?_assertMatch(
+      {'BoolTest', _, _},
+      #'BoolTest'{b=null,s=null}
+    )},
+    {"StructA record", ?_assertMatch({'StructA', _}, #'StructA'{s=null})},
+    {"StructB record", ?_assertMatch(
+      {'StructB', _, _},
+      #'StructB'{aa=null,ab=null}
+    )}
+  ].
+
+struct_info_test_() ->
+  [
+    {"Bonk definition (short version)", ?_assertEqual(
+      {struct, [{1, string}, {2, i32}]},
+      thrift_test_types:struct_info('Bonk')
+    )},
+    {"Bonk definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, string, message, undefined},
+        {2, undefined, i32, type, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('Bonk')
+    )},
+    {"Bools definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, bool, im_true, undefined},
+        {2, undefined, bool, im_false, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('Bools')
+    )},
+    {"Xtruct definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, string, string_thing, undefined},
+        {4, undefined, byte, byte_thing, undefined},
+        {9, undefined, i32, i32_thing, undefined},
+        {11, undefined, i64, i64_thing, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('Xtruct')
+    )},
+    {"Xtruct2 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, byte, byte_thing, undefined},
+        {2, undefined, {struct, {'thrift_test_types', 'Xtruct'}}, struct_thing, #'Xtruct'{}},
+        {3, undefined, i32, i32_thing, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('Xtruct2')
+    )},
+    {"Xtruct3 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, string, string_thing, undefined},
+        {4, undefined, i32, changed, undefined},
+        {9, undefined, i32, i32_thing, undefined},
+        {11, undefined, i64, i64_thing, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('Xtruct3')
+    )},
+    {"Insanity definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {map, i32, i64}, userMap, dict:new()},
+        {2, undefined, {list, {struct, {'thrift_test_types', 'Xtruct'}}}, xtructs, []}
+      ]},
+      thrift_test_types:struct_info_ext('Insanity')
+    )},
+    {"CrazyNesting definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, string, string_field, undefined},
+        {2, optional, {set, {struct, {'thrift_test_types', 'Insanity'}}}, set_field, sets:new()},
+        {3, required, {list, {map, 
+          {set, i32},
+          {map, i32, {set, {list, {map, {struct, {'thrift_test_types', 'Insanity'}}, string}}}}
+        }}, list_field, []},
+        {4, undefined, string, binary_field, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('CrazyNesting')
+    )},
+    {"Xception definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, i32, errorCode, undefined},
+        {2, undefined, string, message, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('Xception')
+    )},
+    {"Xception2 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, i32, errorCode, undefined},
+        {2, undefined, {struct, {'thrift_test_types', 'Xtruct'}}, struct_thing, #'Xtruct'{}}
+      ]},
+      thrift_test_types:struct_info_ext('Xception2')
+    )},
+    {"EmptyStruct definition", ?_assertEqual(
+      {struct, []},
+      thrift_test_types:struct_info_ext('EmptyStruct')
+    )},
+    {"OneField definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {struct, {'thrift_test_types', 'EmptyStruct'}}, field, #'EmptyStruct'{}}
+      ]},
+      thrift_test_types:struct_info_ext('OneField')
+    )},
+    {"VersioningTestV1 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, i32, begin_in_both, undefined},
+        {3, undefined, string, old_string, undefined},
+        {12, undefined, i32, end_in_both, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('VersioningTestV1')
+    )},
+    {"VersioningTestV2 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, i32, begin_in_both, undefined},
+        {2, undefined, i32, newint, undefined},
+        {3, undefined, byte, newbyte, undefined},
+        {4, undefined, i16, newshort, undefined},
+        {5, undefined, i64, newlong, undefined},
+        {6, undefined, double, newdouble, undefined},
+        {7, undefined, {struct, {thrift_test_types, 'Bonk'}}, newstruct, #'Bonk'{}},
+        {8, undefined, {list, i32}, newlist, []},
+        {9, undefined, {set, i32}, newset, sets:new()},
+        {10, undefined, {map, i32, i32}, newmap, dict:new()},
+        {11, undefined, string, newstring, undefined},
+        {12, undefined, i32, end_in_both, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('VersioningTestV2')
+    )},
+    {"ListTypeVersioningV1 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, i32}, myints, []},
+        {2, undefined, string, hello, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('ListTypeVersioningV1')
+    )},
+    {"ListTypeVersioningV2 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, string}, strings, []},
+        {2, undefined, string, hello, undefined}
+      ]},
+      thrift_test_types:struct_info_ext('ListTypeVersioningV2')
+    )},
+    {"GuessProtocolStruct definition", ?_assertEqual(
+      {struct, [
+        {7, undefined, {map, string, string}, map_field, dict:new()}
+      ]},
+      thrift_test_types:struct_info_ext('GuessProtocolStruct')
+    )},
+    {"LargeDeltas definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {struct, {thrift_test_types, 'Bools'}}, b1, #'Bools'{}},
+        {10, undefined, {struct, {thrift_test_types, 'Bools'}}, b10, #'Bools'{}},
+        {100, undefined, {struct, {thrift_test_types, 'Bools'}}, b100, #'Bools'{}},
+        {500, undefined, bool, check_true, undefined},
+        {1000, undefined, {struct, {thrift_test_types, 'Bools'}}, b1000, #'Bools'{}},
+        {1500, undefined, bool, check_false, undefined},
+        {2000, undefined, {struct, {thrift_test_types, 'VersioningTestV2'}}, vertwo2000, #'VersioningTestV2'{}},
+        {2500, undefined, {set, string}, a_set2500, sets:new()},
+        {3000, undefined, {struct, {thrift_test_types, 'VersioningTestV2'}}, vertwo3000, #'VersioningTestV2'{}},
+        {4000, undefined, {list, i32}, big_numbers, []}
+      ]},
+      thrift_test_types:struct_info_ext('LargeDeltas')
+    )},
+    {"NestedListsI32x2 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, {list, i32}}, integerlist, []}
+      ]},
+      thrift_test_types:struct_info_ext('NestedListsI32x2')
+    )},
+    {"NestedListsI32x3 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, {list, {list, i32}}}, integerlist, []}
+      ]},
+      thrift_test_types:struct_info_ext('NestedListsI32x3')
+    )},
+    {"NestedMixedx2 definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, {set, i32}}, int_set_list, []},
+        {2, undefined, {map, i32, {set, string}}, map_int_strset, dict:new()},
+        {3, undefined, {list, {map, i32, {set, string}}}, map_int_strset_list, []}
+      ]},
+      thrift_test_types:struct_info_ext('NestedMixedx2')
+    )},
+    {"ListBonks definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, {struct, {thrift_test_types, 'Bonk'}}}, bonk, []}
+      ]},
+      thrift_test_types:struct_info_ext('ListBonks')
+    )},
+    {"NestedListsBonk definition", ?_assertEqual(
+      {struct, [
+        {1, undefined, {list, {list, {list, {struct, {thrift_test_types, 'Bonk'}}}}}, bonk, []}
+      ]},
+      thrift_test_types:struct_info_ext('NestedListsBonk')
+    )},
+    {"BoolTest definition", ?_assertEqual(
+      {struct, [
+        {1, optional, bool, b, true},
+        {2, optional, string, s, "true"}
+      ]},
+      thrift_test_types:struct_info_ext('BoolTest')
+    )},
+    {"StructA definition", ?_assertEqual(
+      {struct, [{1, required, string, s, undefined}]},
+      thrift_test_types:struct_info_ext('StructA')
+    )},
+    {"StructB definition", ?_assertEqual(
+      {struct, [
+        {1, optional, {struct, {thrift_test_types, 'StructA'}}, aa, #'StructA'{}},
+        {2, required, {struct, {thrift_test_types, 'StructA'}}, ab, #'StructA'{}}
+      ]},
+      thrift_test_types:struct_info_ext('StructB')
+    )}
+  ].
+
+service_info_test_() ->
+  [
+    {"testVoid params", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testVoid, params_type)
+    )},
+    {"testVoid reply", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testVoid, reply_type)
+    )},
+    {"testVoid exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testVoid, exceptions)
+    )},
+    {"testString params", ?_assertEqual(
+      {struct, [{1, string}]},
+      thrift_test_thrift:function_info(testString, params_type)
+    )},
+    {"testString reply", ?_assertEqual(
+      string,
+      thrift_test_thrift:function_info(testString, reply_type)
+    )},
+    {"testString exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testString, exceptions)
+    )},
+    {"testByte params", ?_assertEqual(
+      {struct, [{1, byte}]},
+      thrift_test_thrift:function_info(testByte, params_type)
+    )},
+    {"testByte reply", ?_assertEqual(
+      byte,
+      thrift_test_thrift:function_info(testByte, reply_type)
+    )},
+    {"testByte exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testByte, exceptions)
+    )},
+    {"testI32 params", ?_assertEqual(
+      {struct, [{1, i32}]},
+      thrift_test_thrift:function_info(testI32, params_type)
+    )},
+    {"testI32 reply", ?_assertEqual(
+      i32,
+      thrift_test_thrift:function_info(testI32, reply_type)
+    )},
+    {"testI32 exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testI32, exceptions)
+    )},
+    {"testI64 params", ?_assertEqual(
+      {struct, [{1, i64}]},
+      thrift_test_thrift:function_info(testI64, params_type)
+    )},
+    {"testI64 reply", ?_assertEqual(
+      i64,
+      thrift_test_thrift:function_info(testI64, reply_type)
+    )},
+    {"testI64 exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testI64, exceptions)
+    )},
+    {"testDouble params", ?_assertEqual(
+      {struct, [{1, double}]},
+      thrift_test_thrift:function_info(testDouble, params_type)
+    )},
+    {"testDouble reply", ?_assertEqual(
+      double,
+      thrift_test_thrift:function_info(testDouble, reply_type)
+    )},
+    {"testDouble exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testDouble, exceptions)
+    )},
+    {"testStruct params", ?_assertEqual(
+      {struct, [
+        {1, {struct, {thrift_test_types, 'Xtruct'}}}
+      ]},
+      thrift_test_thrift:function_info(testStruct, params_type)
+    )},
+    {"testStruct reply", ?_assertEqual(
+      {struct, {thrift_test_types, 'Xtruct'}},
+      thrift_test_thrift:function_info(testStruct, reply_type)
+    )},
+    {"testStruct exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testStruct, exceptions)
+    )},
+    {"testNest params", ?_assertEqual(
+      {struct, [
+        {1, {struct, {thrift_test_types, 'Xtruct2'}}}
+      ]},
+      thrift_test_thrift:function_info(testNest, params_type)
+    )},
+    {"testNest reply", ?_assertEqual(
+      {struct, {thrift_test_types, 'Xtruct2'}},
+      thrift_test_thrift:function_info(testNest, reply_type)
+    )},
+    {"testNest exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testNest, exceptions)
+    )},
+    {"testMap params", ?_assertEqual(
+      {struct, [
+        {1, {map, i32, i32}}
+      ]},
+      thrift_test_thrift:function_info(testMap, params_type)
+    )},
+    {"testMap reply", ?_assertEqual(
+      {map, i32, i32},
+      thrift_test_thrift:function_info(testMap, reply_type)
+    )},
+    {"testMap exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testMap, exceptions)
+    )},
+    {"testStringMap params", ?_assertEqual(
+      {struct, [
+        {1, {map, string, string}}
+      ]},
+      thrift_test_thrift:function_info(testStringMap, params_type)
+    )},
+    {"testStringMap reply", ?_assertEqual(
+      {map, string, string},
+      thrift_test_thrift:function_info(testStringMap, reply_type)
+    )},
+    {"testStringMap exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testStringMap, exceptions)
+    )},
+    {"testSet params", ?_assertEqual(
+      {struct, [
+        {1, {set, i32}}
+      ]},
+      thrift_test_thrift:function_info(testSet, params_type)
+    )},
+    {"testSet reply", ?_assertEqual(
+      {set, i32},
+      thrift_test_thrift:function_info(testSet, reply_type)
+    )},
+    {"testSet exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testSet, exceptions)
+    )},
+    {"testList params", ?_assertEqual(
+      {struct, [
+        {1, {list, i32}}
+      ]},
+      thrift_test_thrift:function_info(testList, params_type)
+    )},
+    {"testList reply", ?_assertEqual(
+      {list, i32},
+      thrift_test_thrift:function_info(testList, reply_type)
+    )},
+    {"testList exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testList, exceptions)
+    )},
+    {"testEnum params", ?_assertEqual(
+      {struct, [
+        {1, i32}
+      ]},
+      thrift_test_thrift:function_info(testEnum, params_type)
+    )},
+    {"testEnum reply", ?_assertEqual(
+      i32,
+      thrift_test_thrift:function_info(testEnum, reply_type)
+    )},
+    {"testEnum exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testEnum, exceptions)
+    )},
+    {"testTypedef params", ?_assertEqual(
+      {struct, [{1, i64}]},
+      thrift_test_thrift:function_info(testTypedef, params_type)
+    )},
+    {"testTypedef reply", ?_assertEqual(
+      i64,
+      thrift_test_thrift:function_info(testTypedef, reply_type)
+    )},
+    {"testTypedef exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testTypedef, exceptions)
+    )},
+    {"testMapMap params", ?_assertEqual(
+      {struct, [
+        {1, i32}
+      ]},
+      thrift_test_thrift:function_info(testMapMap, params_type)
+    )},
+    {"testMapMap reply", ?_assertEqual(
+      {map, i32, {map, i32,i32}},
+      thrift_test_thrift:function_info(testMapMap, reply_type)
+    )},
+    {"testMapMap exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testMapMap, exceptions)
+    )},
+    {"testInsanity params", ?_assertEqual(
+      {struct, [
+        {1, {struct, {thrift_test_types, 'Insanity'}}}
+      ]},
+      thrift_test_thrift:function_info(testInsanity, params_type)
+    )},
+    {"testInsanity reply", ?_assertEqual(
+      {map, i64, {map, i32, {struct, {'thrift_test_types', 'Insanity'}}}},
+      thrift_test_thrift:function_info(testInsanity, reply_type)
+    )},
+    {"testInsanity exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testInsanity, exceptions)
+    )},
+    {"testMulti params", ?_assertEqual(
+      {struct, [
+        {1, byte},
+        {2, i32},
+        {3, i64},
+        {4, {map, i16, string}},
+        {5, i32},
+        {6, i64}
+      ]},
+      thrift_test_thrift:function_info(testMulti, params_type)
+    )},
+    {"testMulti reply", ?_assertEqual(
+      {struct, {thrift_test_types, 'Xtruct'}},
+      thrift_test_thrift:function_info(testMulti, reply_type)
+    )},
+    {"testMulti exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testMulti, exceptions)
+    )},
+    {"testException params", ?_assertEqual(
+      {struct, [{1, string}]},
+      thrift_test_thrift:function_info(testException, params_type)
+    )},
+    {"testException reply", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testException, reply_type)
+    )},
+    {"testException exceptions", ?_assertEqual(
+      {struct, [
+        {1, {struct, {thrift_test_types, 'Xception'}}}
+      ]},
+      thrift_test_thrift:function_info(testException, exceptions)
+    )},
+    {"testMultiException params", ?_assertEqual(
+      {struct, [{1, string}, {2, string}]},
+      thrift_test_thrift:function_info(testMultiException, params_type)
+    )},
+    {"testMultiException reply", ?_assertEqual(
+      {struct, {thrift_test_types, 'Xtruct'}},
+      thrift_test_thrift:function_info(testMultiException, reply_type)
+    )},
+    {"testMultiException exceptions", ?_assertEqual(
+      {struct, [
+        {1, {struct, {thrift_test_types, 'Xception'}}},
+        {2, {struct, {thrift_test_types, 'Xception2'}}}
+      ]},
+      thrift_test_thrift:function_info(testMultiException, exceptions)
+    )},
+    {"testOneway params", ?_assertEqual(
+      {struct, [{1, i32}]},
+      thrift_test_thrift:function_info(testOneway, params_type)
+    )},
+    {"testOneway reply", ?_assertEqual(
+      oneway_void,
+      thrift_test_thrift:function_info(testOneway, reply_type)
+    )},
+    {"testOneway exceptions", ?_assertEqual(
+      {struct, []},
+      thrift_test_thrift:function_info(testOneway, exceptions)
+    )},
+    {"blahBlah params", ?_assertEqual(
+      {struct, []},
+      second_service_thrift:function_info(blahBlah, params_type)
+    )},
+    {"blahBlah reply", ?_assertEqual(
+      {struct, []},
+      second_service_thrift:function_info(blahBlah, reply_type)
+    )},
+    {"blahBlah exceptions", ?_assertEqual(
+      {struct, []},
+      second_service_thrift:function_info(blahBlah, exceptions)
+    )},
+    {"secondtestString params", ?_assertEqual(
+      {struct, [{1, string}]},
+      second_service_thrift:function_info(secondtestString, params_type)
+    )},
+    {"secondtestString reply", ?_assertEqual(
+      string,
+      second_service_thrift:function_info(secondtestString, reply_type)
+    )},
+    {"secondtestString exceptions", ?_assertEqual(
+      {struct, []},
+      second_service_thrift:function_info(secondtestString, exceptions)
+    )}
+  ].
\ No newline at end of file