Fetch and upload corpus seed from/to GCS

Test: HAL Fuzz
Bug: 64022625
Merged-In: I947e02a9f00a099addbc629256e308614846ea8a
Change-Id: I947e02a9f00a099addbc629256e308614846ea8a
diff --git a/template/func_fuzzer_test/func_fuzzer_test.py b/template/func_fuzzer_test/func_fuzzer_test.py
index 62da005..b9ea472 100644
--- a/template/func_fuzzer_test/func_fuzzer_test.py
+++ b/template/func_fuzzer_test/func_fuzzer_test.py
@@ -24,6 +24,7 @@
 from vts.utils.python.controllers import adb
 from vts.utils.python.controllers import android_device
 from vts.utils.python.common import vts_spec_utils
+from vts.utils.python.fuzzer import corpus_manager
 
 from vts.testcases.fuzz.template.libfuzzer_test import libfuzzer_test_config as config
 from vts.testcases.fuzz.template.libfuzzer_test import libfuzzer_test
@@ -57,6 +58,7 @@
         self._vts_spec_parser = vts_spec_utils.VtsSpecParser(
             self.data_file_path)
         self._temp_dir = tempfile.mkdtemp()
+        self._corpus_manager = corpus_manager.CorpusManager(self.user_params)
 
     def _RegisteredInterfaces(self, hal_package):
         """Returns a list of registered interfaces for a given hal package.
diff --git a/template/libfuzzer_test/libfuzzer_test.py b/template/libfuzzer_test/libfuzzer_test.py
index 5c1ec6f..cbdf464 100644
--- a/template/libfuzzer_test/libfuzzer_test.py
+++ b/template/libfuzzer_test/libfuzzer_test.py
@@ -91,47 +91,111 @@
         corpus_out = test_case.GetCorpusOutDir()
         self._dut.adb.shell('mkdir %s -p' % corpus_out)
 
-    # TODO(b/64022625): retrieve from GCS to host
     def RetrieveCorpusSeed(self, test_case):
-        """Retrieves corpus seed directory from the host.
+        """Retrieves corpus seed directory from GCS to the target.
 
         Args:
             test_case: LibFuzzerTestCase object, current test case.
 
         Throws:
             throws an AdbError when there is an error in adb operations.
+
+        Returns:
+            inuse_seed, the file path of the inuse seed in GCS, if fetch succeeded.
+            None, otherwise.
         """
-        host_corpus_seed = os.path.join(
+        inuse_seed = self._corpus_manager.FetchCorpusSeed(
+            test_case._test_name, self._temp_dir)
+        local_corpus_seed = os.path.join(
             self._temp_dir, '%s_corpus_seed' % test_case._test_name)
-        if not os.path.exists(host_corpus_seed):
+        if inuse_seed is not None and os.path.exists(
+                local_corpus_seed) and os.listdir(local_corpus_seed):
+            self._dut.adb.push(local_corpus_seed, config.FUZZER_TEST_DIR)
+        else:
             corpus_seed = test_case.GetCorpusSeedDir()
             self._dut.adb.shell('mkdir %s -p' % corpus_seed)
+        return inuse_seed
+
+    def AnalyzeGeneratedCorpus(self, test_case):
+        """Analyzes the generated corpus body.
+
+        Args:
+            test_case: LibFuzzerTestCase object.
+
+        Returns:
+            number of newly generated corpus strings, if the out directory exists.
+            0, otherwise.
+        """
+        logging.info('temporary directory for this test: %s', self._temp_dir)
+        pulled_corpus_out_dir = os.path.join(
+            self._temp_dir, os.path.basename(test_case.GetCorpusOutDir()))
+        if os.path.exists(pulled_corpus_out_dir):
+            logging.info('corpus out directory pulled from target: %s',
+                         pulled_corpus_out_dir)
+            pulled_corpus = os.listdir(pulled_corpus_out_dir)
+            logging.debug(pulled_corpus)
+            logging.info('generated corpus size: %d', len(pulled_corpus))
+            return len(pulled_corpus)
         else:
-            self._dut.adb.push(host_corpus_seed, config.FUZZER_TEST_DIR)
+            logging.error('corput out directory does not exist on the host.')
+            return 0
+
+    def EvaluateTestcase(self, test_case, result, inuse_seed):
+        """Evaluates the test result and moves the used seed accordingly.
+
+        Args:
+            test_case: LibFuzzerTestCase object.
+            result: a result dict object returned by the adb shell command.
+            inuse_seed: the seed used as input to this test case.
+
+        Raises:
+            signals.TestFailure when the testcase failed.
+        """
+        if 'return_codes' in result and \
+            result['return_codes'] == config.ExitCode.FUZZER_TEST_PASS:
+            logging.info('adb shell fuzzing command exited normally.')
+            if inuse_seed is not None:
+                self._corpus_manager.InuseToComplete(test_case._test_name,
+                                                     inuse_seed)
+        elif 'return_codes' in result and \
+            result['return_codes'] == config.ExitCode.FUZZER_TEST_FAIL:
+            logging.info('adb shell fuzzing command exited normally.')
+            if inuse_seed is not None:
+                self._corpus_manager.InuseToCrash(test_case._test_name,
+                                                  inuse_seed)
+        else:
+            logging.error('adb shell fuzzing command exited abnormally.')
+            #TODO(b/64123979): once normal fail happens, change
+            if inuse_seed is not None:
+                self._corpus_manager.InuseToCrash(test_case._test_name,
+                                                  inuse_seed)
 
     def RunTestcase(self, test_case):
         """Runs the given test case and asserts the result.
 
         Args:
-            test_case: LibFuzzerTestCase object
+            test_case: LibFuzzerTestCase object.
         """
         self.PushFiles(test_case.bin_host_path)
         self.CreateCorpusOut(test_case)
-        self.RetrieveCorpusSeed(test_case)
+        inuse_seed = self.RetrieveCorpusSeed(test_case)
         fuzz_cmd = '"%s"' % test_case.GetRunCommand()
-        result = self._dut.adb.shell(fuzz_cmd, no_except=True)
 
-        self._dut.adb.pull(test_case.GetCorpusOutDir(), self._temp_dir)
-        pulled_corpus_out_dir = os.path.join(
-            self._temp_dir, os.path.basename(test_case.GetCorpusOutDir()))
-        pulled_corpus = os.listdir(pulled_corpus_out_dir)
-        logging.info(
-            'temporary corpus directory %s created', pulled_corpus_out_dir)
-        for file in pulled_corpus:
-            logging.debug('binary string %s generated', file)
-        logging.info('%d binary strings generated', len(pulled_corpus))
-        # TODO(b/64022625): update from tmp to GCS
-        # TODO(b/64022625): possibly upload crash log for if needed
+        result = {}
+        try:
+            result = self._dut.adb.shell(fuzz_cmd, no_except=True)
+        except adb.AdbError as e:
+            logging.exception(e)
+
+        try:
+            self._dut.adb.pull(test_case.GetCorpusOutDir(), self._temp_dir)
+            self.AnalyzeGeneratedCorpus(test_case)
+            self._corpus_manager.UploadCorpusOutDir(test_case._test_name,
+                                                    self._temp_dir)
+        except adb.AdbError as e:
+            logging.exception(e)
+
+        self.EvaluateTestcase(test_case, result, inuse_seed)
         self.AssertTestResult(test_case, result)
 
     def LogCrashReport(self, test_case):
@@ -185,6 +249,7 @@
 
         exit_code = result[const.EXIT_CODE]
         if exit_code == config.ExitCode.FUZZER_TEST_FAIL:
+            #TODO(b/64123979): once normal fail happens, examine.
             self.LogCrashReport(test_case)
             asserts.fail('%s failed normally.' % test_case.test_name)
         elif exit_code != config.ExitCode.FUZZER_TEST_PASS: