Fail fast on pub dependencies problem.

Fail fast on pub version dependencies problem and tests not timing out
properly.

Change-Id: Ib80de197b97549cf94f2c3a6da8c7555d84cd7cb
Bug: https://github.com/flutter/flutter/issues/79375
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/12020
Reviewed-by: Keyong Han <keyonghan@google.com>
Commit-Queue: Godofredo Contreras <godofredoc@google.com>
diff --git a/recipe_modules/test_utils/__init__.py b/recipe_modules/test_utils/__init__.py
index ceaa420..a3030c1 100644
--- a/recipe_modules/test_utils/__init__.py
+++ b/recipe_modules/test_utils/__init__.py
@@ -1,4 +1,5 @@
 DEPS = [
     'recipe_engine/raw_io',
     'recipe_engine/step',
+    'recipe_engine/swarming',
 ]
diff --git a/recipe_modules/test_utils/api.py b/recipe_modules/test_utils/api.py
index 254115b..c8aba61 100644
--- a/recipe_modules/test_utils/api.py
+++ b/recipe_modules/test_utils/api.py
@@ -4,39 +4,52 @@
 
 from recipe_engine import recipe_api
 
-
 # The maximum number of lines to be included in the step summary for a failed
 # test.
 SUMMARY_MAX_LINES = 200
 
+# Default timeout for tests seconds
+TIMEOUT_SECS = 3600
+
 
 class TestUtilsApi(recipe_api.RecipeApi):
   """Utilities to run flutter tests."""
 
-  def run_test(self, step_name, command_list):
+  def is_devicelab_bot(self):
+    """Whether the current bot is a devicelab bot or not."""
+    return (
+        str(self.m.swarming.bot_id).startswith('flutter-devicelab') or
+        str(self.m.swarming.bot_id).startswith('flutter-win')
+    )
+
+  def run_test(self, step_name, command_list, timeout_secs=TIMEOUT_SECS):
     """Recipe's step wrapper to collect stdout and add it to step_summary.
 
     Args:
       step_name(str): The name of the step.
       command_list(list(str)): A list of strings with the command and
         parameters to execute.
+      timeout_secs(int): The timeout in seconds for this step.
     """
     try:
       self.m.step(
           step_name,
           command_list,
           stdout=self.m.raw_io.output_text(),
-          stderr=self.m.raw_io.output_text()
+          stderr=self.m.raw_io.output_text(),
+          timeout=timeout_secs
       )
     except self.m.step.StepFailure as f:
       result = f.result
       # Truncate stdout
       lines = result.stdout.split("\n")
-      stdout_lines = lines[-SUMMARY_MAX_LINES] if len(lines) > SUMMARY_MAX_LINES else lines
+      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] if len(lines) > SUMMARY_MAX_LINES else lines
+      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)
     finally:
diff --git a/recipe_modules/test_utils/examples/full.expected/failing.json b/recipe_modules/test_utils/examples/full.expected/failing.json
index ee71fac..639d022 100644
--- a/recipe_modules/test_utils/examples/full.expected/failing.json
+++ b/recipe_modules/test_utils/examples/full.expected/failing.json
@@ -5,6 +5,7 @@
       "-la"
     ],
     "name": "mytest",
+    "timeout": 3600,
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@stdout@#failure@@@",
       "@@@STEP_LOG_LINE@stdout@this is a failure@@@",
diff --git a/recipe_modules/test_utils/examples/full.expected/passing.json b/recipe_modules/test_utils/examples/full.expected/passing.json
index 7b6c25e..499fa10 100644
--- a/recipe_modules/test_utils/examples/full.expected/passing.json
+++ b/recipe_modules/test_utils/examples/full.expected/passing.json
@@ -5,6 +5,7 @@
       "-la"
     ],
     "name": "mytest",
+    "timeout": 3600,
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@stdout@@@@",
       "@@@STEP_LOG_END@stdout@@@",
diff --git a/recipe_modules/test_utils/examples/full.py b/recipe_modules/test_utils/examples/full.py
index b34b147..fa61d9b 100644
--- a/recipe_modules/test_utils/examples/full.py
+++ b/recipe_modules/test_utils/examples/full.py
@@ -13,6 +13,7 @@
 
 def RunSteps(api):
   api.test_utils.run_test('mytest', ['ls', '-la'])
+  api.test_utils.is_devicelab_bot()
 
 
 def GenTests(api):
diff --git a/recipes/flutter/flutter_drone.expected/android_sdk.json b/recipes/flutter/flutter_drone.expected/android_sdk.json
index 31e09e0..c4ae808 100644
--- a/recipes/flutter/flutter_drone.expected/android_sdk.json
+++ b/recipes/flutter/flutter_drone.expected/android_sdk.json
@@ -330,7 +330,8 @@
       ]
     },
     "infra_step": true,
-    "name": "download dependencies"
+    "name": "download dependencies",
+    "timeout": 300
   },
   {
     "cmd": [
@@ -403,6 +404,7 @@
       ]
     },
     "name": "run test.dart for None shard and subshard None",
+    "timeout": 1800,
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@stdout@@@@",
       "@@@STEP_LOG_END@stdout@@@",
diff --git a/recipes/flutter/flutter_drone.expected/no_requirements.json b/recipes/flutter/flutter_drone.expected/no_requirements.json
index c5e8d07..11431e8 100644
--- a/recipes/flutter/flutter_drone.expected/no_requirements.json
+++ b/recipes/flutter/flutter_drone.expected/no_requirements.json
@@ -146,7 +146,8 @@
       ]
     },
     "infra_step": true,
-    "name": "download dependencies"
+    "name": "download dependencies",
+    "timeout": 300
   },
   {
     "cmd": [
@@ -203,6 +204,7 @@
       ]
     },
     "name": "run test.dart for None shard and subshard None",
+    "timeout": 1800,
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@stdout@@@@",
       "@@@STEP_LOG_END@stdout@@@",
diff --git a/recipes/flutter/flutter_drone.expected/web_engine.json b/recipes/flutter/flutter_drone.expected/web_engine.json
index 8e8b544..f9d601d 100644
--- a/recipes/flutter/flutter_drone.expected/web_engine.json
+++ b/recipes/flutter/flutter_drone.expected/web_engine.json
@@ -146,7 +146,8 @@
       ]
     },
     "infra_step": true,
-    "name": "download dependencies"
+    "name": "download dependencies",
+    "timeout": 300
   },
   {
     "cmd": [],
@@ -327,6 +328,7 @@
       ]
     },
     "name": "run test.dart for None shard and subshard None",
+    "timeout": 1800,
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@stdout@@@@",
       "@@@STEP_LOG_END@stdout@@@",
diff --git a/recipes/flutter/flutter_drone.expected/xcode.json b/recipes/flutter/flutter_drone.expected/xcode.json
index c5e8d07..11431e8 100644
--- a/recipes/flutter/flutter_drone.expected/xcode.json
+++ b/recipes/flutter/flutter_drone.expected/xcode.json
@@ -146,7 +146,8 @@
       ]
     },
     "infra_step": true,
-    "name": "download dependencies"
+    "name": "download dependencies",
+    "timeout": 300
   },
   {
     "cmd": [
@@ -203,6 +204,7 @@
       ]
     },
     "name": "run test.dart for None shard and subshard None",
+    "timeout": 1800,
     "~followup_annotations": [
       "@@@STEP_LOG_LINE@stdout@@@@",
       "@@@STEP_LOG_END@stdout@@@",
diff --git a/recipes/flutter/flutter_drone.py b/recipes/flutter/flutter_drone.py
index 2daa27a..1c79e43 100644
--- a/recipes/flutter/flutter_drone.py
+++ b/recipes/flutter/flutter_drone.py
@@ -21,6 +21,10 @@
     'recipe_engine/step',
 ]
 
+# Default timeouts for framework tests.
+HOSTONLY_TIMEOUT_SECS = 30 * 60
+DEVICELAB_TIMEOUT_SECS = 10 * 60
+
 
 def RunShard(api, env, env_prefixes, checkout_path):
   with api.context(env=env, env_prefixes=env_prefixes, cwd=checkout_path):
@@ -33,11 +37,15 @@
       local_engine_path = api.path.abs_to_path(str(env.get('LOCAL_ENGINE')))
       dart_bin = local_engine_path.join('dart-sdk', 'bin')
       env_prefixes = {'PATH': ['%s' % str(dart_bin)]}
+    # Default timeout for tasks in either devicelab or hostonly.
+    deps_timeout_secs = DEVICELAB_TIMEOUT_SECS if api.test_utils.is_devicelab_bot(
+    ) else HOSTONLY_TIMEOUT_SECS
     with api.context(env=env, env_prefixes=env_prefixes):
       api.test_utils.run_test(
           'run test.dart for %s shard and subshard %s' %
           (api.properties.get('shard'), api.properties.get('subshard')),
-          cmd_list
+          cmd_list,
+          timeout_secs=deps_timeout_secs
       )
 
 
@@ -61,10 +69,12 @@
   env['SUBSHARD'] = api.properties.get('subshard')
 
   with api.context(env=env, env_prefixes=env_prefixes, cwd=checkout_path):
+    # Dependencies timeout.
+    deps_timeout_secs = 300
     api.step(
-        'download dependencies',
-        ['flutter', 'update-packages'],
+        'download dependencies', ['flutter', 'update-packages'],
         infra_step=True,
+        timeout=deps_timeout_secs
     )
     # Load local engine information if available.
     api.flutter_deps.flutter_engine(env, env_prefixes)
diff --git a/recipes/plugins/plugins.expected/basic.json b/recipes/plugins/plugins.expected/basic.json
index e0a2021..6daae74 100644
--- a/recipes/plugins/plugins.expected/basic.json
+++ b/recipes/plugins/plugins.expected/basic.json
@@ -316,6 +316,7 @@
     },
     "infra_step": true,
     "name": "prepare environment.download dependencies",
+    "timeout": 300,
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/recipes/plugins/plugins.py b/recipes/plugins/plugins.py
index 439d392..7caee4f 100644
--- a/recipes/plugins/plugins.py
+++ b/recipes/plugins/plugins.py
@@ -42,10 +42,13 @@
           infra_step=True,
       )
       api.step('flutter doctor', ['flutter', 'doctor'])
+      # Fail fast on dependencies problem.
+      timeout_secs = 300
       api.step(
           'download dependencies',
           ['flutter', 'update-packages'],
           infra_step=True,
+          timeout=timeout_secs
       )
       api.step(
           'pub global activate flutter_plugin_tools',