Merge "ASuite: update prebuilt acloud by uploader.(6637661)."
diff --git a/atest/linux-x86/atest-py3 b/atest/linux-x86/atest-py3
index 182e616..e0bb5d4 100755
--- a/atest/linux-x86/atest-py3
+++ b/atest/linux-x86/atest-py3
Binary files differ
diff --git a/atest/smoke_test_data/iterations.py b/atest/smoke_test_data/iterations.py
new file mode 100644
index 0000000..8b63e92
--- /dev/null
+++ b/atest/smoke_test_data/iterations.py
@@ -0,0 +1,100 @@
+# Copyright 2020, The Android Open Source Project
+#
+# Licensed 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 inspect
+import os
+import subprocess
+import sys
+
+import utils
+
+# Must assign TITLE so that smoke_tests is able to print it.
+TITLE = "Iteration Runs"
+ATEST = "atest"
+ARGS = "-c {}={} {}"
+ITERS = "--iterations"
+RERUN = "--rerun-until-failure"
+RETRY = "--retry-any-failure"
+RUNS = 5
+PASS_TEST_NAME = "atest_will_pass_tests"
+FAIL_TEST_NAME = "atest_will_fail_tests"
+
+
+class Iterations():
+    """Class of testing --iterations, --rerun-until-failure,
+    --retry-any-failure arguments."""
+
+    def run_test(self, test_arg, expected_pass):
+        """Define pass_tests/fail_tests from expect_pass.
+
+        The test result will be like running:
+        $ atest -c --retry-any-failure=5 atest_will_fail_tests
+
+        Args:
+            test_arg: A string of "--iterations", etc.
+            expected_pass: A boolean of expected test result.
+        """
+        if expected_pass:
+            args = ARGS.format(test_arg, RUNS, PASS_TEST_NAME)
+            pass_tests = 3
+            fail_tests = 0
+        else:
+            args = ARGS.format(test_arg, RUNS, FAIL_TEST_NAME)
+            pass_tests = 1
+            fail_tests = 2
+        # Define expected_threads from the argument.
+        if test_arg == ITERS:
+            expected_threads = (pass_tests+fail_tests) * RUNS
+        elif test_arg == RERUN:
+            expected_threads = (pass_tests+fail_tests) * 1
+        elif test_arg == RETRY:
+            expected_threads = pass_tests + (fail_tests*RUNS)
+
+        print('test{}'.format(test_arg).replace('-', '_'))
+        cmd = '{} {}'.format(ATEST, args)
+        print('Running: {}'.format(cmd))
+        # 1. Check return code
+        if expected_pass:
+            if subprocess.call(cmd, shell=True) != 0:
+                print('Failed testing:\n {}'.format(cmd))
+                sys.exit(1)
+        else:
+            if subprocess.call(cmd, shell=True) == 0:
+                print('Testing:\n{}\nis expected failure,'
+                      ' but it passed.'.format(cmd))
+                sys.exit(1)
+
+        # 2. Should observe same threads in host_log.
+        if not utils.is_identical(expected_threads,
+                                  utils.get_test_threads(),
+                                  "Test threads"):
+            sys.exit(1)
+
+        # 3. Should observe same amount of passed/failed test in end_host_log.
+        if not utils.has_correct_passed_failed_counts(pass_tests, fail_tests):
+            sys.exit(1)
+
+
+def main():
+    utils.print_banner(TITLE)
+    iters = Iterations()
+    # There are 3 test arguments:
+    # "--iterations": runs atest_will_pass_test and expect pass.
+    # "--rerun-until-failure": runs atest_will_fail_test and expect fail.
+    # "--retry-any-failure": runs atest_will_fail_test and expect fail.
+    test_args = [(ITERS, True), (RERUN, False), (RETRY, False)]
+    for i, _ in enumerate(test_args):
+        print('\n[{}/{}] '.format(i+1, len(test_args)), end='')
+        utils.func_wrapper(iters.run_test, test_args[i])
+    utils.print_banner(TITLE, True)
diff --git a/atest/smoke_test_data/utils.py b/atest/smoke_test_data/utils.py
index 722a4a6..8690d75 100644
--- a/atest/smoke_test_data/utils.py
+++ b/atest/smoke_test_data/utils.py
@@ -129,3 +129,59 @@
         args: A list of argument.
     """
     func(*args)
+
+
+def get_test_threads():
+    """Get the number of running threads.
+
+    If 3 tests within the testable module, and the iteration number is 5,
+    the ModuleListener will appear 15 times.
+
+    Returns:
+        An integer that ModuleListener appears in host_log.
+    """
+    cmd = ('zcat /tmp/atest_result/LATEST/log/in*/host_log*.zip'
+           '| grep "ModuleListener" | wc -l')
+    return subprocess.check_output(cmd, shell=True).decode().strip()
+
+
+def get_passed_counts():
+    """Get the number of PASSED in end_host_log.
+
+    Returns:
+        An integer that shows PASSED in end_host_log.
+    """
+    cmd = ('zcat /tmp/atest_result/LATEST/log/in*/end_host_log*.zip'
+           '| grep PASSED | awk -F": " \'{{print $2}}\'')
+    return subprocess.check_output(cmd, shell=True).decode().strip()
+
+
+def get_failed_counts():
+    """Get the number of FAILED in end_host_log.
+
+    Returns:
+        An integer that shows PASSED in end_host_log.
+    """
+    cmd = ('zcat /tmp/atest_result/LATEST/log/in*/end_host_log*.zip'
+           '| grep FAILED | awk -F": " \'{{print $2}}\'')
+    return subprocess.check_output(cmd, shell=True).decode().strip()
+
+
+def has_correct_passed_failed_counts(passes, failures):
+    """Given desired passed and failed numbers, and return if they are the same
+    as expectation.
+
+    Args:
+        passes: An integer of desired passed number.
+        failures: An integer of desired failed number.
+
+    Returns:
+        A boolean: True if both passed/failed numbers match the result,
+            otherwise False.
+    """
+    if not is_identical(passes, get_passed_counts(), "PASSED number"):
+        return False
+    if not is_identical(failures, get_failed_counts(), "FAILED number"):
+        return False
+    return True
+
diff --git a/atest/smoke_tests b/atest/smoke_tests
index b894cbd..77b39f6 100755
--- a/atest/smoke_tests
+++ b/atest/smoke_tests
@@ -30,7 +30,7 @@
 # Append more tests in the ordered list below, and provide a main() so that
 # smoke_tests can invoke them directly.
 # TODO: b/153411501 support --enable-file-patterns in the future.
-TESTS = ['lookup_tests', 'test_mappings', 'include_subdirs']
+TESTS = ['lookup_tests', 'test_mappings', 'include_subdirs', 'iterations']
 
 
 if __name__ == '__main__':