Apply test_runner step to devicelab tests.

This also fixes problems with logs presenting a single line instead of
the entire summary.

Change-Id: I30e58390c20cfcddcbaa5126b5440435b5ae9dab
Bug: https://github.com/flutter/flutter/issues/64208
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/12022
Reviewed-by: Keyong Han <keyonghan@google.com>
Commit-Queue: Godofredo Contreras <godofredoc@google.com>
diff --git a/recipe_modules/test_utils/api.py b/recipe_modules/test_utils/api.py
index c8aba61..368263d 100644
--- a/recipe_modules/test_utils/api.py
+++ b/recipe_modules/test_utils/api.py
@@ -42,16 +42,16 @@
     except self.m.step.StepFailure as f:
       result = f.result
       # Truncate stdout
-      lines = result.stdout.split("\n")
-      stdout_lines = lines[-SUMMARY_MAX_LINES
+      lines = result.stdout.splitlines()
+      stdout_lines = lines[-SUMMARY_MAX_LINES:
                           ] if len(lines) > SUMMARY_MAX_LINES else lines
       stdout = '\n'.join(stdout_lines)
       # Truncate stderr
-      lines = result.stderr.split("\n")
-      stderr_lines = lines[-SUMMARY_MAX_LINES
+      lines = result.stderr.splitlines()
+      stderr_lines = lines[-SUMMARY_MAX_LINES:
                           ] if len(lines) > SUMMARY_MAX_LINES else lines
       stderr = '\n'.join(stderr_lines)
-      raise self.m.step.StepFailure(stdout or stderr)
+      raise self.m.step.StepFailure('\n\n```%s```\n' % (stdout or stderr))
     finally:
       self.m.step.active_result.presentation.logs[
           'stdout'] = self.m.step.active_result.stdout
diff --git a/recipe_modules/test_utils/examples/full.expected/failing.json b/recipe_modules/test_utils/examples/full.expected/failing.json
index 639d022..bfad3a4 100644
--- a/recipe_modules/test_utils/examples/full.expected/failing.json
+++ b/recipe_modules/test_utils/examples/full.expected/failing.json
@@ -18,7 +18,7 @@
   {
     "failure": {
       "failure": {},
-      "humanReason": "#failure\nthis is a failure"
+      "humanReason": "\n\n```#failure\nthis is a failure```\n"
     },
     "name": "$result"
   }
diff --git a/recipes/devicelab/devicelab_drone.expected/basic.json b/recipes/devicelab/devicelab_drone.expected/basic.json
index 449402b..c4460d3 100644
--- a/recipes/devicelab/devicelab_drone.expected/basic.json
+++ b/recipes/devicelab/devicelab_drone.expected/basic.json
@@ -299,7 +299,14 @@
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
-    "name": "run abc"
+    "name": "run abc",
+    "timeout": 3600,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@@@@",
+      "@@@STEP_LOG_END@stdout@@@",
+      "@@@STEP_LOG_LINE@stderr@@@@",
+      "@@@STEP_LOG_END@stderr@@@"
+    ]
   },
   {
     "cmd": [],
diff --git a/recipes/devicelab/devicelab_drone.expected/no-task-name.json b/recipes/devicelab/devicelab_drone.expected/no-task-name.json
index 6758846..74f823d 100644
--- a/recipes/devicelab/devicelab_drone.expected/no-task-name.json
+++ b/recipes/devicelab/devicelab_drone.expected/no-task-name.json
@@ -7,7 +7,7 @@
       "The recipe has crashed at point 'Uncaught exception'!",
       "",
       "Traceback (most recent call last):",
-      "  File \"RECIPE_REPO[flutter]/recipes/devicelab/devicelab_drone.py\", line 31, in RunSteps",
+      "  File \"RECIPE_REPO[flutter]/recipes/devicelab/devicelab_drone.py\", line 32, in RunSteps",
       "    raise ValueError('A task_name property is required')",
       "ValueError: A task_name property is required"
     ]
diff --git a/recipes/devicelab/devicelab_drone.expected/post-submit.json b/recipes/devicelab/devicelab_drone.expected/post-submit.json
index d31c960..ebf20f1 100644
--- a/recipes/devicelab/devicelab_drone.expected/post-submit.json
+++ b/recipes/devicelab/devicelab_drone.expected/post-submit.json
@@ -298,7 +298,14 @@
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
-    "name": "run abc"
+    "name": "run abc",
+    "timeout": 3600,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@@@@",
+      "@@@STEP_LOG_END@stdout@@@",
+      "@@@STEP_LOG_LINE@stderr@@@@",
+      "@@@STEP_LOG_END@stderr@@@"
+    ]
   },
   {
     "cmd": [],
diff --git a/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json b/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json
index 3b4a1ae..17b4c09 100644
--- a/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json
+++ b/recipes/devicelab/devicelab_drone.expected/upload-metrics-mac.json
@@ -324,7 +324,14 @@
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
-    "name": "run abc"
+    "name": "run abc",
+    "timeout": 3600,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@@@@",
+      "@@@STEP_LOG_END@stdout@@@",
+      "@@@STEP_LOG_LINE@stderr@@@@",
+      "@@@STEP_LOG_END@stderr@@@"
+    ]
   },
   {
     "cmd": [],
diff --git a/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json b/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json
index c78ec15..b831ea4b 100644
--- a/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json
+++ b/recipes/devicelab/devicelab_drone.expected/xcode-chromium-mac.json
@@ -324,7 +324,14 @@
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
-    "name": "run abc"
+    "name": "run abc",
+    "timeout": 3600,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@@@@",
+      "@@@STEP_LOG_END@stdout@@@",
+      "@@@STEP_LOG_LINE@stderr@@@@",
+      "@@@STEP_LOG_END@stderr@@@"
+    ]
   },
   {
     "cmd": [],
diff --git a/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json b/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json
index c78ec15..b831ea4b 100644
--- a/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json
+++ b/recipes/devicelab/devicelab_drone.expected/xcode-devicelab.json
@@ -324,7 +324,14 @@
         "[CLEANUP]/tmp_tmp_2/vpython"
       ]
     },
-    "name": "run abc"
+    "name": "run abc",
+    "timeout": 3600,
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@stdout@@@@",
+      "@@@STEP_LOG_END@stdout@@@",
+      "@@@STEP_LOG_LINE@stderr@@@@",
+      "@@@STEP_LOG_END@stderr@@@"
+    ]
   },
   {
     "cmd": [],
diff --git a/recipes/devicelab/devicelab_drone.py b/recipes/devicelab/devicelab_drone.py
index c6bfb51..b8b84b3 100644
--- a/recipes/devicelab/devicelab_drone.py
+++ b/recipes/devicelab/devicelab_drone.py
@@ -12,6 +12,7 @@
     'flutter/repo_util',
     'flutter/os_utils',
     'flutter/osx_sdk',
+    'flutter/test_utils',
     'recipe_engine/buildbucket',
     'recipe_engine/context',
     'recipe_engine/file',
@@ -82,7 +83,7 @@
         api.step('flutter doctor', ['flutter', 'doctor', '--verbose'])
         test_runner_command = ['dart', 'bin/run.dart']
         test_runner_command.extend(runner_params)
-        api.step('run %s' % task_name, test_runner_command)
+        api.test_utils.run_test('run %s' % task_name, test_runner_command)
         api.logs_util.upload_logs(task_name)
         # This is to clean up leaked processes.
         api.os_utils.kill_processes()
@@ -105,7 +106,7 @@
     api.step('Set execute permission', ['chmod', '755', resource_name])
     test_runner_command = [resource_name]
     test_runner_command.extend(runner_params)
-    api.step('run %s' % task_name, test_runner_command)
+    api.test_utils.run_test('run %s' % task_name, test_runner_command)
     api.logs_util.upload_logs(task_name)
     # This is to clean up leaked processes.
     api.os_utils.kill_processes()