Add testing and auto-publish features to release_aar.py.

Bug: webrtc:8365
Change-Id: I6dda3773d62ea37d371933691144499ba17898d7
Reviewed-on: https://webrtc-review.googlesource.com/7614
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20274}
diff --git a/tools_webrtc/android/release_aar.py b/tools_webrtc/android/release_aar.py
index f4be14f..c2ae065 100644
--- a/tools_webrtc/android/release_aar.py
+++ b/tools_webrtc/android/release_aar.py
@@ -15,6 +15,7 @@
 """
 
 import argparse
+import json
 import logging
 import os
 import re
@@ -37,20 +38,35 @@
 
 
 ARCHS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']
-REPOSITORY_API = 'https://api.bintray.com/content/google/webrtc/google-webrtc'
+MAVEN_REPOSITORY = 'https://google.bintray.com/webrtc'
+API = 'https://api.bintray.com'
+PACKAGE_PATH = 'google/webrtc/google-webrtc'
+CONTENT_API = API + '/content/' + PACKAGE_PATH
+PACKAGES_API = API + '/packages/' + PACKAGE_PATH
 GROUP_ID = 'org/webrtc'
 ARTIFACT_ID = 'google-webrtc'
 COMMIT_POSITION_REGEX = r'^Cr-Commit-Position: refs/heads/master@{#(\d+)}$'
-UPLOAD_TIMEOUT_SECONDS = 10.0
+API_TIMEOUT_SECONDS = 10.0
 UPLOAD_TRIES = 3
 # The sleep time is increased exponentially.
 UPLOAD_RETRY_BASE_SLEEP_SECONDS = 2.0
+GRADLEW_BIN = os.path.join(CHECKOUT_ROOT,
+                           'examples/androidtests/third_party/gradle/gradlew')
+AAR_PROJECT_DIR = os.path.join(CHECKOUT_ROOT, 'examples/aarproject')
+AAR_PROJECT_GRADLE = os.path.join(AAR_PROJECT_DIR, 'build.gradle')
+AAR_PROJECT_APP_GRADLE = os.path.join(AAR_PROJECT_DIR, 'app', 'build.gradle')
+AAR_PROJECT_DEPENDENCY = "implementation 'org.webrtc:google-webrtc:1.0.+'"
+AAR_PROJECT_VERSION_DEPENDENCY = "implementation 'org.webrtc:google-webrtc:%s'"
 
 
 def _ParseArgs():
   parser = argparse.ArgumentParser(description='Releases WebRTC on Bintray.')
   parser.add_argument('--use-goma', action='store_true', default=False,
       help='Use goma.')
+  parser.add_argument('--skip-tests', action='store_true', default=False,
+      help='Skips running the tests.')
+  parser.add_argument('--publish', action='store_true', default=False,
+      help='Automatically publishes the library if the tests pass.')
   parser.add_argument('--verbose', action='store_true', default=False,
       help='Debug logging.')
   return parser.parse_args()
@@ -82,7 +98,7 @@
 
   target_dir = version + '/' + GROUP_ID + '/' + ARTIFACT_ID + '/' + version
   target_path = target_dir + '/' + target_file
-  url = REPOSITORY_API + '/' + target_path
+  url = CONTENT_API + '/' + target_path
 
   logging.info('Uploading %s to %s', filename, url)
   with open(filename) as fh:
@@ -91,7 +107,7 @@
   for attempt in xrange(UPLOAD_TRIES):
     try:
       response = requests.put(url, data=file_data, auth=(user, password),
-                              timeout=UPLOAD_TIMEOUT_SECONDS)
+                              timeout=API_TIMEOUT_SECONDS)
       break
     except requests.exceptions.Timeout as e:
       logging.warning('Timeout while uploading: %s', e)
@@ -114,7 +130,96 @@
     fh.write(pom)
 
 
-def ReleaseAar(use_goma):
+def _TestAAR(tmp_dir, username, password, version):
+  """Runs AppRTCMobile tests using the AAR. Returns true if the tests pass."""
+  logging.info('Testing library.')
+  env = jinja2.Environment(
+    loader=jinja2.PackageLoader('release_aar'),
+  )
+
+  gradle_backup = os.path.join(tmp_dir, 'build.gradle.backup')
+  app_gradle_backup = os.path.join(tmp_dir, 'app-build.gradle.backup')
+
+  # Make backup copies of the project files before modifying them.
+  shutil.copy2(AAR_PROJECT_GRADLE, gradle_backup)
+  shutil.copy2(AAR_PROJECT_APP_GRADLE, app_gradle_backup)
+
+  try:
+    maven_repository_template = env.get_template('maven-repository.jinja')
+    maven_repository = maven_repository_template.render(
+        url=MAVEN_REPOSITORY, username=username, password=password)
+
+    # Append Maven repository to build file to download unpublished files.
+    with open(AAR_PROJECT_GRADLE, 'a') as gradle_file:
+      gradle_file.write(maven_repository)
+
+    # Read app build file.
+    with open(AAR_PROJECT_APP_GRADLE, 'r') as gradle_app_file:
+      gradle_app = gradle_app_file.read()
+
+    if AAR_PROJECT_DEPENDENCY not in gradle_app:
+      raise Exception(
+          '%s not found in the build file.' % AAR_PROJECT_DEPENDENCY)
+    # Set version to the version to be tested.
+    target_dependency = AAR_PROJECT_VERSION_DEPENDENCY % version
+    gradle_app = gradle_app.replace(AAR_PROJECT_DEPENDENCY, target_dependency)
+
+    # Write back.
+    with open(AAR_PROJECT_APP_GRADLE, 'w') as gradle_app_file:
+      gradle_app_file.write(gradle_app)
+
+    # Run tests.
+    try:
+      # First clean the project.
+      subprocess.check_call([GRADLEW_BIN, 'clean'], cwd=AAR_PROJECT_DIR)
+      # Then run the tests.
+      subprocess.check_call([GRADLEW_BIN, 'connectedDebugAndroidTest'],
+                            cwd=AAR_PROJECT_DIR)
+    except subprocess.CalledProcessError:
+      logging.exception('Test failure.')
+      return False  # Clean or tests failed
+
+    return True  # Tests pass
+  finally:
+    # Restore backups.
+    shutil.copy2(gradle_backup, AAR_PROJECT_GRADLE)
+    shutil.copy2(app_gradle_backup, AAR_PROJECT_APP_GRADLE)
+
+
+def _PublishAAR(user, password, version, additional_args):
+  args = {
+    'publish_wait_for_secs': 0  # Publish asynchronously.
+  }
+  args.update(additional_args)
+
+  url = CONTENT_API + '/' + version + '/publish'
+  response = requests.post(url, data=json.dumps(args), auth=(user, password),
+                           timeout=API_TIMEOUT_SECONDS)
+
+  if not response.ok:
+    raise Exception('Failed to publish. Response: %s' % response)
+
+
+def _DeleteUnpublishedVersion(user, password, version):
+  url = PACKAGES_API + '/versions/' + version
+  response = requests.get(url, auth=(user, password),
+                          timeout=API_TIMEOUT_SECONDS)
+  if not response.ok:
+    raise Exception('Failed to get version info. Response: %s' % response)
+
+  version_info = json.loads(response.content)
+  if version_info['published']:
+    logging.info('Version has already been published, not deleting.')
+    return
+
+  logging.info('Deleting unpublished version.')
+  response = requests.delete(url, auth=(user, password),
+                             timeout=API_TIMEOUT_SECONDS)
+  if not response.ok:
+    raise Exception('Failed to delete version. Response: %s' % response)
+
+
+def ReleaseAar(use_goma, skip_tests, publish):
   version = '1.0.' + _GetCommitPos()
   commit = _GetCommitHash()
   logging.info('Releasing AAR version %s with hash %s', version, commit)
@@ -143,17 +248,28 @@
     _UploadFile(user, api_key, third_party_licenses_file, version,
                 'THIRD_PARTY_LICENSES.md')
     _UploadFile(user, api_key, pom_file, version, base_name + '.pom')
+
+    tests_pass = skip_tests or _TestAAR(tmp_dir, user, api_key, version)
+    if not tests_pass:
+      logging.info('Discarding library.')
+      _PublishAAR(user, api_key, version, {'discard': True})
+      _DeleteUnpublishedVersion(user, api_key, version)
+      raise Exception('Test failure. Discarded library.')
+
+    if publish:
+      logging.info('Publishing library.')
+      _PublishAAR(user, api_key, version, {})
+    else:
+      logging.info('Note: The library has not not been published automatically.'
+                   ' Please do so manually if desired.')
   finally:
     shutil.rmtree(tmp_dir, True)
 
-  logging.info('Library successfully uploaded. Please test and publish it on '
-               'Bintray.')
-
 
 def main():
   args = _ParseArgs()
   logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
-  ReleaseAar(args.use_goma)
+  ReleaseAar(args.use_goma, args.skip_tests, args.publish)
 
 
 if __name__ == '__main__':