THRIFT-3531 Create cross lang feature test for string and container read length limit

This closes #780
diff --git a/test/features/container_limit.py b/test/features/container_limit.py
new file mode 100644
index 0000000..4a7da60
--- /dev/null
+++ b/test/features/container_limit.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+import argparse
+import sys
+
+from util import add_common_args, init_protocol
+from local_thrift import thrift
+from thrift.Thrift import TMessageType, TType
+
+
+# TODO: generate from ThriftTest.thrift
+def test_list(proto, value):
+  method_name = 'testList'
+  ttype = TType.LIST
+  etype = TType.I32
+  proto.writeMessageBegin(method_name, TMessageType.CALL, 3)
+  proto.writeStructBegin(method_name + '_args')
+  proto.writeFieldBegin('thing', ttype, 1)
+  proto.writeListBegin(etype, len(value))
+  for e in value:
+    proto.writeI32(e)
+  proto.writeListEnd()
+  proto.writeFieldEnd()
+  proto.writeFieldStop()
+  proto.writeStructEnd()
+  proto.writeMessageEnd()
+  proto.trans.flush()
+
+  _, mtype, _ = proto.readMessageBegin()
+  assert mtype == TMessageType.REPLY
+  proto.readStructBegin()
+  _, ftype, fid = proto.readFieldBegin()
+  assert fid == 0
+  assert ftype == ttype
+  etype2, len2 = proto.readListBegin()
+  assert etype == etype2
+  assert len2 == len(value)
+  for i in range(len2):
+    v = proto.readI32()
+    assert v == value[i]
+  proto.readListEnd()
+  proto.readFieldEnd()
+  _, ftype, _ = proto.readFieldBegin()
+  assert ftype == TType.STOP
+  proto.readStructEnd()
+  proto.readMessageEnd()
+
+
+def main(argv):
+  p = argparse.ArgumentParser()
+  add_common_args(p)
+  p.add_argument('--limit', type=int)
+  args = p.parse_args()
+  proto = init_protocol(args)
+  # TODO: test set and map
+  test_list(proto, list(range(args.limit - 1)))
+  test_list(proto, list(range(args.limit - 1)))
+  print('[OK]: limit - 1')
+  test_list(proto, list(range(args.limit)))
+  test_list(proto, list(range(args.limit)))
+  print('[OK]: just limit')
+  try:
+    test_list(proto, list(range(args.limit + 1)))
+  except:
+    print('[OK]: limit + 1')
+  else:
+    print('[ERROR]: limit + 1')
+    assert False
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/test/features/known_failures_Linux.json b/test/features/known_failures_Linux.json
new file mode 100644
index 0000000..edff41a
--- /dev/null
+++ b/test/features/known_failures_Linux.json
@@ -0,0 +1,46 @@
+[
+  "c_glib-limit_container_length_binary_buffered-ip",
+  "c_glib-limit_string_length_binary_buffered-ip",
+  "csharp-limit_container_length_binary_buffered-ip",
+  "csharp-limit_container_length_compact_buffered-ip",
+  "csharp-limit_string_length_binary_buffered-ip",
+  "csharp-limit_string_length_compact_buffered-ip",
+  "erl-limit_container_length_binary_buffered-ip",
+  "erl-limit_container_length_compact_buffered-ip",
+  "erl-limit_string_length_binary_buffered-ip",
+  "erl-limit_string_length_compact_buffered-ip",
+  "go-limit_container_length_binary_buffered-ip",
+  "go-limit_container_length_compact_buffered-ip",
+  "go-limit_string_length_binary_buffered-ip",
+  "go-limit_string_length_compact_buffered-ip",
+  "hs-limit_container_length_binary_buffered-ip",
+  "hs-limit_container_length_compact_buffered-ip",
+  "hs-limit_string_length_binary_buffered-ip",
+  "hs-limit_string_length_compact_buffered-ip",
+  "java-limit_container_length_binary_buffered-ip",
+  "java-limit_container_length_compact_buffered-ip",
+  "java-limit_string_length_binary_buffered-ip",
+  "java-limit_string_length_compact_buffered-ip",
+  "nodejs-limit_container_length_binary_buffered-ip",
+  "nodejs-limit_container_length_compact_buffered-ip",
+  "nodejs-limit_string_length_binary_buffered-ip",
+  "nodejs-limit_string_length_compact_buffered-ip",
+  "perl-limit_container_length_binary_buffered-ip",
+  "perl-limit_string_length_binary_buffered-ip",
+  "py-limit_container_length_accel-binary_buffered-ip",
+  "py-limit_container_length_binary_buffered-ip",
+  "py-limit_container_length_compact_buffered-ip",
+  "py-limit_string_length_accel-binary_buffered-ip",
+  "py-limit_string_length_binary_buffered-ip",
+  "py-limit_string_length_compact_buffered-ip",
+  "py3-limit_container_length_binary_buffered-ip",
+  "py3-limit_container_length_compact_buffered-ip",
+  "py3-limit_string_length_binary_buffered-ip",
+  "py3-limit_string_length_compact_buffered-ip",
+  "rb-limit_container_length_accel-binary_buffered-ip",
+  "rb-limit_container_length_binary_buffered-ip",
+  "rb-limit_container_length_compact_buffered-ip",
+  "rb-limit_string_length_accel-binary_buffered-ip",
+  "rb-limit_string_length_binary_buffered-ip",
+  "rb-limit_string_length_compact_buffered-ip"
+]
\ No newline at end of file
diff --git a/test/features/string_limit.py b/test/features/string_limit.py
new file mode 100644
index 0000000..b4d48ac
--- /dev/null
+++ b/test/features/string_limit.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+import argparse
+import sys
+
+from util import add_common_args, init_protocol
+from local_thrift import thrift
+from thrift.Thrift import TMessageType, TType
+
+
+# TODO: generate from ThriftTest.thrift
+def test_string(proto, value):
+  method_name = 'testString'
+  ttype = TType.STRING
+  proto.writeMessageBegin(method_name, TMessageType.CALL, 3)
+  proto.writeStructBegin(method_name + '_args')
+  proto.writeFieldBegin('thing', ttype, 1)
+  proto.writeString(value)
+  proto.writeFieldEnd()
+  proto.writeFieldStop()
+  proto.writeStructEnd()
+  proto.writeMessageEnd()
+  proto.trans.flush()
+
+  _, mtype, _ = proto.readMessageBegin()
+  assert mtype == TMessageType.REPLY
+  proto.readStructBegin()
+  _, ftype, fid = proto.readFieldBegin()
+  assert fid == 0
+  assert ftype == ttype
+  result = proto.readString()
+  proto.readFieldEnd()
+  _, ftype, _ = proto.readFieldBegin()
+  assert ftype == TType.STOP
+  proto.readStructEnd()
+  proto.readMessageEnd()
+  assert value == result
+
+
+def main(argv):
+  p = argparse.ArgumentParser()
+  add_common_args(p)
+  p.add_argument('--limit', type=int)
+  args = p.parse_args()
+  proto = init_protocol(args)
+  test_string(proto, 'a' * (args.limit - 1))
+  test_string(proto, 'a' * (args.limit - 1))
+  print('[OK]: limit - 1')
+  test_string(proto, 'a' * args.limit)
+  test_string(proto, 'a' * args.limit)
+  print('[OK]: just limit')
+  try:
+    test_string(proto, 'a' * (args.limit + 1))
+  except:
+    print('[OK]: limit + 1')
+  else:
+    print('[ERROR]: limit + 1')
+    assert False
+
+if __name__ == '__main__':
+  main(sys.argv[1:])
diff --git a/test/features/tests.json b/test/features/tests.json
index 0836309..f726dad 100644
--- a/test/features/tests.json
+++ b/test/features/tests.json
@@ -23,5 +23,41 @@
     "transports": ["buffered"],
     "sockets": ["ip"],
     "workdir": "features"
+  },
+  {
+    "name": "limit_string_length",
+    "command": [
+      "python",
+      "string_limit.py",
+      "--limit=50"
+    ],
+    "remote_args": [
+      "--string-limit=50"
+    ],
+    "protocols": [
+      "binary",
+      "compact"
+    ],
+    "transports": ["buffered"],
+    "sockets": ["ip"],
+    "workdir": "features"
+  },
+  {
+    "name": "limit_container_length",
+    "command": [
+      "python",
+      "container_limit.py",
+      "--limit=50"
+    ],
+    "remote_args": [
+      "--container-limit=50"
+    ],
+    "protocols": [
+      "binary",
+      "compact"
+    ],
+    "transports": ["buffered"],
+    "sockets": ["ip"],
+    "workdir": "features"
   }
 ]
diff --git a/test/features/util.py b/test/features/util.py
index cff7ff8..e364136 100644
--- a/test/features/util.py
+++ b/test/features/util.py
@@ -1,11 +1,20 @@
 import argparse
+import socket
+
+from local_thrift import thrift
+from thrift.transport.TSocket import TSocket
+from thrift.transport.TTransport import TBufferedTransport, TFramedTransport
+from thrift.transport.THttpClient import THttpClient
+from thrift.protocol.TBinaryProtocol import TBinaryProtocol
+from thrift.protocol.TCompactProtocol import TCompactProtocol
+from thrift.protocol.TJSONProtocol import TJSONProtocol
 
 
 def add_common_args(p):
   p.add_argument('--host', default='localhost')
-  p.add_argument('--port', type=int)
-  p.add_argument('--protocol')
-  p.add_argument('--transport')
+  p.add_argument('--port', type=int, default=9090)
+  p.add_argument('--protocol', default='binary')
+  p.add_argument('--transport', default='buffered')
   p.add_argument('--ssl', action='store_true')
 
 
@@ -13,3 +22,19 @@
   p = argparse.ArgumentParser()
   add_common_args(p)
   return p.parse_args(argv)
+
+
+def init_protocol(args):
+  sock = TSocket(args.host, args.port, socket_family=socket.AF_INET)
+  sock.setTimeout(500)
+  trans = {
+    'buffered': TBufferedTransport,
+    'framed': TFramedTransport,
+    'http': THttpClient,
+  }[args.transport](sock)
+  trans.open()
+  return {
+    'binary': TBinaryProtocol,
+    'compact': TCompactProtocol,
+    'json': TJSONProtocol,
+  }[args.protocol](trans)