THRIFT-847 Test Framework harmonization across all languages
THRIFT-2946 Enhance usability of cross test framework

Patch: Nobuaki Sukegawa

This closes: #358
diff --git a/test/crossrunner/test.py b/test/crossrunner/test.py
new file mode 100644
index 0000000..512e664
--- /dev/null
+++ b/test/crossrunner/test.py
@@ -0,0 +1,136 @@
+#
+# 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.
+#
+
+import copy
+import multiprocessing
+import os
+import sys
+
+from crossrunner.util import merge_dict
+
+
+def domain_socket_path(port):
+  return '/tmp/ThriftTest.thrift.%d' % port
+
+
+class TestProgram(object):
+  def __init__(self, kind, name, protocol, transport, socket, workdir, command, env=None,
+               extra_args=[], join_args=False, **kwargs):
+    self.kind = kind
+    self.name = name
+    self.protocol = protocol
+    self.transport = transport
+    self.socket = socket
+    self.workdir = workdir
+    self.command = None
+    self._base_command = self._fix_cmd_path(command)
+    if env:
+      self.env = copy.copy(os.environ)
+      self.env.update(env)
+    else:
+      self.env = os.environ
+    self._extra_args = extra_args
+    self._join_args = join_args
+
+  def _fix_cmd_path(self, cmd):
+    # if the arg is a file in the current directory, make it path
+    def abs_if_exists(arg):
+      p = os.path.join(self.workdir, arg)
+      return p if os.path.exists(p) else arg
+
+    if cmd[0] == 'python':
+      cmd[0] = sys.executable
+    else:
+      cmd[0] = abs_if_exists(cmd[0])
+    return cmd
+
+  def _socket_arg(self, socket, port):
+    return {
+      'ip-ssl': '--ssl',
+      'domain': '--domain-socket=%s' % domain_socket_path(port),
+    }.get(socket, None)
+
+  def build_command(self, port):
+    cmd = copy.copy(self._base_command)
+    args = []
+    args.append('--protocol=' + self.protocol)
+    args.append('--transport=' + self.transport)
+    socket_arg = self._socket_arg(self.socket, port)
+    if socket_arg:
+      args.append(socket_arg)
+    args.append('--port=%d' % port)
+    if self._join_args:
+      cmd.append('%s' % " ".join(args))
+    else:
+      cmd.extend(args)
+    if self._extra_args:
+      cmd.extend(self._extra_args)
+    self.command = cmd
+    return self.command
+
+
+class TestEntry(object):
+  def __init__(self, testdir, server, client, delay, timeout, **kwargs):
+    self.testdir = testdir
+    self._log = multiprocessing.get_logger()
+    self._config = kwargs
+    self.protocol = kwargs['protocol']
+    self.transport = kwargs['transport']
+    self.socket = kwargs['socket']
+    self.server = TestProgram('server', **self._fix_workdir(merge_dict(self._config, server)))
+    self.client = TestProgram('client', **self._fix_workdir(merge_dict(self._config, client)))
+    self.delay = delay
+    self.timeout = timeout
+    self._name = None
+    # results
+    self.success = None
+    self.as_expected = None
+    self.returncode = None
+    self.expired = False
+
+  def _fix_workdir(self, config):
+    key = 'workdir'
+    path = config.get(key, None)
+    if not path:
+      path = self.testdir
+    if os.path.isabs(path):
+      path = os.path.realpath(path)
+    else:
+      path = os.path.realpath(os.path.join(self.testdir, path))
+    config.update({key: path})
+    return config
+
+  @classmethod
+  def get_name(cls, server, client, proto, trans, sock, *args):
+    return '%s-%s_%s_%s-%s' % (server, client, proto, trans, sock)
+
+  @property
+  def name(self):
+    if not self._name:
+      self._name = self.get_name(
+          self.server.name, self.client.name, self.protocol, self.transport, self.socket)
+    return self._name
+
+  @property
+  def transport_name(self):
+    return '%s-%s' % (self.transport, self.socket)
+
+
+def test_name(server, client, protocol, transport, socket, **kwargs):
+  return TestEntry.get_name(server['name'], client['name'], protocol, transport, socket)