|  | @@ -391,7 +391,7 @@ def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
 | 
	
		
			
				|  |  |            cmdline=cmdline,
 | 
	
		
			
				|  |  |            cwd=cwd,
 | 
	
		
			
				|  |  |            environ=environ,
 | 
	
		
			
				|  |  | -          shortname="%s:%s:%s" % (suite_name, language, test_case),
 | 
	
		
			
				|  |  | +          shortname='%s:%s:%s' % (suite_name, language, test_case),
 | 
	
		
			
				|  |  |            timeout_seconds=2*60,
 | 
	
		
			
				|  |  |            flake_retries=5 if args.allow_flakes else 0,
 | 
	
		
			
				|  |  |            timeout_retries=2 if args.allow_flakes else 0,
 | 
	
	
		
			
				|  | @@ -423,7 +423,7 @@ def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
 | 
	
		
			
				|  |  |            cmdline=cmdline,
 | 
	
		
			
				|  |  |            cwd=cwd,
 | 
	
		
			
				|  |  |            environ=environ,
 | 
	
		
			
				|  |  | -          shortname="cloud_to_cloud:%s:%s_server:%s" % (language, server_name,
 | 
	
		
			
				|  |  | +          shortname='cloud_to_cloud:%s:%s_server:%s' % (language, server_name,
 | 
	
		
			
				|  |  |                                                   test_case),
 | 
	
		
			
				|  |  |            timeout_seconds=2*60,
 | 
	
		
			
				|  |  |            flake_retries=5 if args.allow_flakes else 0,
 | 
	
	
		
			
				|  | @@ -448,7 +448,7 @@ def server_jobspec(language, docker_image):
 | 
	
		
			
				|  |  |    server_job = jobset.JobSpec(
 | 
	
		
			
				|  |  |            cmdline=docker_cmdline,
 | 
	
		
			
				|  |  |            environ=environ,
 | 
	
		
			
				|  |  | -          shortname="interop_server_%s" % language,
 | 
	
		
			
				|  |  | +          shortname='interop_server_%s' % language,
 | 
	
		
			
				|  |  |            timeout_seconds=30*60)
 | 
	
		
			
				|  |  |    server_job.container_name = container_name
 | 
	
		
			
				|  |  |    return server_job
 | 
	
	
		
			
				|  | @@ -467,16 +467,132 @@ def build_interop_image_jobspec(language, tag=None):
 | 
	
		
			
				|  |  |    # TODO(stanleycheung): find a more elegant way to do this
 | 
	
		
			
				|  |  |    if language.safename == 'php' and os.path.exists('/var/local/.composer/auth.json'):
 | 
	
		
			
				|  |  |      env['BUILD_INTEROP_DOCKER_EXTRA_ARGS'] = \
 | 
	
		
			
				|  |  | -      "-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro"
 | 
	
		
			
				|  |  | +      '-v /var/local/.composer/auth.json:/root/.composer/auth.json:ro'
 | 
	
		
			
				|  |  |    build_job = jobset.JobSpec(
 | 
	
		
			
				|  |  |            cmdline=['tools/jenkins/build_interop_image.sh'],
 | 
	
		
			
				|  |  |            environ=env,
 | 
	
		
			
				|  |  | -          shortname="build_docker_%s" % (language),
 | 
	
		
			
				|  |  | +          shortname='build_docker_%s' % (language),
 | 
	
		
			
				|  |  |            timeout_seconds=30*60)
 | 
	
		
			
				|  |  |    build_job.tag = tag
 | 
	
		
			
				|  |  |    return build_job
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +# TODO(adelez): Use mako template.
 | 
	
		
			
				|  |  | +def fill_one_test_result(shortname, resultset, html_str):
 | 
	
		
			
				|  |  | +  if shortname in resultset:
 | 
	
		
			
				|  |  | +    result = resultset[shortname]
 | 
	
		
			
				|  |  | +    if result.state == 'PASSED':
 | 
	
		
			
				|  |  | +      html_str = '%s<td bgcolor=\"green\">PASS</td>\n' % html_str
 | 
	
		
			
				|  |  | +    else:
 | 
	
		
			
				|  |  | +      tooltip = ''
 | 
	
		
			
				|  |  | +      if result.returncode > 0 or result.message:
 | 
	
		
			
				|  |  | +        if result.returncode > 0:
 | 
	
		
			
				|  |  | +          tooltip = 'returncode: %d ' % result.returncode
 | 
	
		
			
				|  |  | +        if result.message:
 | 
	
		
			
				|  |  | +          tooltip = '%smessage: %s' % (tooltip, result.message)     
 | 
	
		
			
				|  |  | +      if result.state == 'FAILED':
 | 
	
		
			
				|  |  | +        html_str = '%s<td bgcolor=\"red\">' % html_str
 | 
	
		
			
				|  |  | +        if tooltip:  
 | 
	
		
			
				|  |  | +          html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
 | 
	
		
			
				|  |  | +                      'data-placement=\"auto\" title=\"%s\">FAIL</a></td>\n' % 
 | 
	
		
			
				|  |  | +                      (html_str, tooltip))
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +          html_str = '%sFAIL</td>\n' % html_str
 | 
	
		
			
				|  |  | +      elif result.state == 'TIMEOUT':
 | 
	
		
			
				|  |  | +        html_str = '%s<td bgcolor=\"yellow\">' % html_str
 | 
	
		
			
				|  |  | +        if tooltip:
 | 
	
		
			
				|  |  | +          html_str = ('%s<a href=\"#\" data-toggle=\"tooltip\" '
 | 
	
		
			
				|  |  | +                      'data-placement=\"auto\" title=\"%s\">TIMEOUT</a></td>\n' 
 | 
	
		
			
				|  |  | +                      % (html_str, tooltip))
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +          html_str = '%sTIMEOUT</td>\n' % html_str
 | 
	
		
			
				|  |  | +  else:
 | 
	
		
			
				|  |  | +    html_str = '%s<td bgcolor=\"magenta\">Not implemented</td>\n' % html_str
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  return html_str
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def render_html_report(test_cases, client_langs, server_langs, resultset,
 | 
	
		
			
				|  |  | +                       num_failures):
 | 
	
		
			
				|  |  | +  """Generate html report."""
 | 
	
		
			
				|  |  | +  sorted_test_cases = sorted(test_cases)
 | 
	
		
			
				|  |  | +  sorted_client_langs = sorted(client_langs)
 | 
	
		
			
				|  |  | +  print sorted_client_langs
 | 
	
		
			
				|  |  | +  sorted_server_langs = sorted(server_langs)
 | 
	
		
			
				|  |  | +  html_str = ('<!DOCTYPE html>\n'
 | 
	
		
			
				|  |  | +              '<html lang=\"en\">\n'
 | 
	
		
			
				|  |  | +              '<head><title>Interop Test Result</title></head>\n'
 | 
	
		
			
				|  |  | +              '<body>\n')
 | 
	
		
			
				|  |  | +  if num_failures > 1:
 | 
	
		
			
				|  |  | +    html_str = (
 | 
	
		
			
				|  |  | +        '%s<p><h2><font color=\"red\">%d tests failed!</font></h2></p>\n' % 
 | 
	
		
			
				|  |  | +        (html_str, num_failures))
 | 
	
		
			
				|  |  | +  elif num_failures:
 | 
	
		
			
				|  |  | +    html_str = (
 | 
	
		
			
				|  |  | +        '%s<p><h2><font color=\"red\">%d test failed!</font></h2></p>\n' % 
 | 
	
		
			
				|  |  | +        (html_str, num_failures))
 | 
	
		
			
				|  |  | +  else:
 | 
	
		
			
				|  |  | +    html_str = (
 | 
	
		
			
				|  |  | +        '%s<p><h2><font color=\"green\">All tests passed!</font></h2></p>\n' % 
 | 
	
		
			
				|  |  | +        html_str)
 | 
	
		
			
				|  |  | +  if args.cloud_to_prod_auth or args.cloud_to_prod:
 | 
	
		
			
				|  |  | +    # Each column header is the client language.
 | 
	
		
			
				|  |  | +    html_str = ('%s<h2>Cloud to Prod</h2>\n' 
 | 
	
		
			
				|  |  | +                '<table style=\"width:100%%\" border=\"1\">\n'
 | 
	
		
			
				|  |  | +                '<tr bgcolor=\"#00BFFF\">\n'
 | 
	
		
			
				|  |  | +                '<th/>\n') % html_str
 | 
	
		
			
				|  |  | +    for client_lang in sorted_client_langs:
 | 
	
		
			
				|  |  | +      html_str = '%s<th>%s\n' % (html_str, client_lang)
 | 
	
		
			
				|  |  | +    html_str = '%s</tr>\n' % html_str
 | 
	
		
			
				|  |  | +    for test_case in sorted_test_cases:
 | 
	
		
			
				|  |  | +      html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, test_case)
 | 
	
		
			
				|  |  | +      for client_lang in sorted_client_langs:
 | 
	
		
			
				|  |  | +        if args.cloud_to_prod:
 | 
	
		
			
				|  |  | +          shortname = 'cloud_to_prod:%s:%s' % (client_lang, test_case)
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +          shortname = 'cloud_to_prod_auth:%s:%s' % (client_lang, test_case)
 | 
	
		
			
				|  |  | +        html_str = fill_one_test_result(shortname, resultset, html_str)       
 | 
	
		
			
				|  |  | +      html_str = '%s</tr>\n' % html_str 
 | 
	
		
			
				|  |  | +    html_str = '%s</table>\n' % html_str
 | 
	
		
			
				|  |  | +  if servers:
 | 
	
		
			
				|  |  | +    for test_case in sorted_test_cases:
 | 
	
		
			
				|  |  | +      # Each column header is the client language.
 | 
	
		
			
				|  |  | +      html_str = ('%s<h2>%s</h2>\n' 
 | 
	
		
			
				|  |  | +                  '<table style=\"width:100%%\" border=\"1\">\n'
 | 
	
		
			
				|  |  | +                  '<tr bgcolor=\"#00BFFF\">\n'
 | 
	
		
			
				|  |  | +                  '<th/>\n') % (html_str, test_case)
 | 
	
		
			
				|  |  | +      for client_lang in sorted_client_langs:
 | 
	
		
			
				|  |  | +        html_str = '%s<th>%s\n' % (html_str, client_lang)
 | 
	
		
			
				|  |  | +      html_str = '%s</tr>\n' % html_str
 | 
	
		
			
				|  |  | +      # Each row head is the server language.
 | 
	
		
			
				|  |  | +      for server_lang in sorted_server_langs:
 | 
	
		
			
				|  |  | +        html_str = '%s<tr><td><b>%s</b></td>\n' % (html_str, server_lang)
 | 
	
		
			
				|  |  | +        # Fill up the cells with test result.
 | 
	
		
			
				|  |  | +        for client_lang in sorted_client_langs:
 | 
	
		
			
				|  |  | +          shortname = 'cloud_to_cloud:%s:%s_server:%s' % (
 | 
	
		
			
				|  |  | +              client_lang, server_lang, test_case)
 | 
	
		
			
				|  |  | +          html_str = fill_one_test_result(shortname, resultset, html_str)
 | 
	
		
			
				|  |  | +        html_str = '%s</tr>\n' % html_str
 | 
	
		
			
				|  |  | +      html_str = '%s</table>\n' % html_str
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  html_str = ('%s\n'
 | 
	
		
			
				|  |  | +              '<script>\n'
 | 
	
		
			
				|  |  | +              '$(document).ready(function(){'
 | 
	
		
			
				|  |  | +              '$(\'[data-toggle=\"tooltip\"]\').tooltip();\n'   
 | 
	
		
			
				|  |  | +              '});\n'
 | 
	
		
			
				|  |  | +              '</script>\n'
 | 
	
		
			
				|  |  | +              '</body>\n'
 | 
	
		
			
				|  |  | +              '</html>') % html_str  
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  # Write to reports/index.html as set up in Jenkins plugin.
 | 
	
		
			
				|  |  | +  html_report_dir = 'reports'
 | 
	
		
			
				|  |  | +  if not os.path.exists(html_report_dir):
 | 
	
		
			
				|  |  | +    os.mkdir(html_report_dir)
 | 
	
		
			
				|  |  | +  html_file_path = os.path.join(html_report_dir, 'index.html')
 | 
	
		
			
				|  |  | +  with open(html_file_path, 'w') as f:
 | 
	
		
			
				|  |  | +    f.write(html_str)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  argp = argparse.ArgumentParser(description='Run interop tests.')
 | 
	
		
			
				|  |  |  argp.add_argument('-l', '--language',
 | 
	
		
			
				|  |  |                    choices=['all'] + sorted(_LANGUAGES),
 | 
	
	
		
			
				|  | @@ -503,7 +619,7 @@ argp.add_argument('-s', '--server',
 | 
	
		
			
				|  |  |                    default=[])
 | 
	
		
			
				|  |  |  argp.add_argument('--override_server',
 | 
	
		
			
				|  |  |                    action='append',
 | 
	
		
			
				|  |  | -                  type=lambda kv: kv.split("="),
 | 
	
		
			
				|  |  | +                  type=lambda kv: kv.split('='),
 | 
	
		
			
				|  |  |                    help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
 | 
	
		
			
				|  |  |                    default=[])
 | 
	
		
			
				|  |  |  argp.add_argument('-t', '--travis',
 | 
	
	
		
			
				|  | @@ -521,7 +637,7 @@ argp.add_argument('--allow_flakes',
 | 
	
		
			
				|  |  |                    default=False,
 | 
	
		
			
				|  |  |                    action='store_const',
 | 
	
		
			
				|  |  |                    const=True,
 | 
	
		
			
				|  |  | -                  help="Allow flaky tests to show as passing (re-runs failed tests up to five times)")
 | 
	
		
			
				|  |  | +                  help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
 | 
	
		
			
				|  |  |  args = argp.parse_args()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  servers = set(s for s in itertools.chain.from_iterable(_SERVERS
 | 
	
	
		
			
				|  | @@ -538,7 +654,7 @@ if args.use_docker:
 | 
	
		
			
				|  |  |      time.sleep(5)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  if not args.use_docker and servers:
 | 
	
		
			
				|  |  | -  print "Running interop servers is only supported with --use_docker option enabled."
 | 
	
		
			
				|  |  | +  print 'Running interop servers is only supported with --use_docker option enabled.'
 | 
	
		
			
				|  |  |    sys.exit(1)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  languages = set(_LANGUAGES[l]
 | 
	
	
		
			
				|  | @@ -560,10 +676,14 @@ if args.use_docker:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    if build_jobs:
 | 
	
		
			
				|  |  |      jobset.message('START', 'Building interop docker images.', do_newline=True)
 | 
	
		
			
				|  |  | -    if jobset.run(build_jobs, newline_on_success=True, maxjobs=args.jobs):
 | 
	
		
			
				|  |  | -      jobset.message('SUCCESS', 'All docker images built successfully.', do_newline=True)
 | 
	
		
			
				|  |  | +    num_failures, _ = jobset.run(
 | 
	
		
			
				|  |  | +        build_jobs, newline_on_success=True, maxjobs=args.jobs)
 | 
	
		
			
				|  |  | +    if num_failures == 0:
 | 
	
		
			
				|  |  | +      jobset.message('SUCCESS', 'All docker images built successfully.', 
 | 
	
		
			
				|  |  | +                     do_newline=True)
 | 
	
		
			
				|  |  |      else:
 | 
	
		
			
				|  |  | -      jobset.message('FAILED', 'Failed to build interop docker images.', do_newline=True)
 | 
	
		
			
				|  |  | +      jobset.message('FAILED', 'Failed to build interop docker images.', 
 | 
	
		
			
				|  |  | +                     do_newline=True)
 | 
	
		
			
				|  |  |        for image in docker_images.itervalues():
 | 
	
		
			
				|  |  |          dockerjob.remove_image(image, skip_nonexistent=True)
 | 
	
		
			
				|  |  |        exit(1);
 | 
	
	
		
			
				|  | @@ -614,7 +734,7 @@ try:
 | 
	
		
			
				|  |  |          jobs.append(test_job)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    if not jobs:
 | 
	
		
			
				|  |  | -    print "No jobs to run."
 | 
	
		
			
				|  |  | +    print 'No jobs to run.'
 | 
	
		
			
				|  |  |      for image in docker_images.itervalues():
 | 
	
		
			
				|  |  |        dockerjob.remove_image(image, skip_nonexistent=True)
 | 
	
		
			
				|  |  |      sys.exit(1)
 | 
	
	
		
			
				|  | @@ -622,13 +742,19 @@ try:
 | 
	
		
			
				|  |  |    root = ET.Element('testsuites')
 | 
	
		
			
				|  |  |    testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  if jobset.run(jobs, newline_on_success=True, maxjobs=args.jobs, xml_report=testsuite):
 | 
	
		
			
				|  |  | -    jobset.message('SUCCESS', 'All tests passed', do_newline=True)
 | 
	
		
			
				|  |  | -  else:
 | 
	
		
			
				|  |  | +  num_failures, resultset = jobset.run(jobs, newline_on_success=True, 
 | 
	
		
			
				|  |  | +                                       maxjobs=args.jobs, xml_report=testsuite)
 | 
	
		
			
				|  |  | +  if num_failures:
 | 
	
		
			
				|  |  |      jobset.message('FAILED', 'Some tests failed', do_newline=True)
 | 
	
		
			
				|  |  | +  else:
 | 
	
		
			
				|  |  | +    jobset.message('SUCCESS', 'All tests passed', do_newline=True)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    tree = ET.ElementTree(root)
 | 
	
		
			
				|  |  |    tree.write('report.xml', encoding='UTF-8')
 | 
	
		
			
				|  |  | +  
 | 
	
		
			
				|  |  | +  # Generate HTML report.
 | 
	
		
			
				|  |  | +  render_html_report(_TEST_CASES, set([str(l) for l in languages]), servers, 
 | 
	
		
			
				|  |  | +                     resultset, num_failures)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  finally:
 | 
	
		
			
				|  |  |    # Check if servers are still running.
 |