Enable FTL reporting on an integration test (#40285)


diff --git a/dev/bots/debug_smoke_ftl.sh b/dev/bots/debug_smoke_ftl.sh
new file mode 100755
index 0000000..bca29b8
--- /dev/null
+++ b/dev/bots/debug_smoke_ftl.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+set -e
+
+GIT_REVISION=$(git rev-parse HEAD)
+
+# New contributors will not have permissions to run this test - they won't be
+# able to access the service account information. We should just mark the test
+# as passed - it will run fine on post submit, where it will still catch
+# failures.
+# We can also still make sure that building a release app bundle still works.
+if [[ $GCLOUD_FIREBASE_TESTLAB_KEY == ENCRYPTED* ]]; then
+  echo "This user does not have permission to run this test."
+  exit 0
+fi
+
+echo $GCLOUD_FIREBASE_TESTLAB_KEY > ${HOME}/gcloud-service-key.json
+gcloud auth activate-service-account --key-file=${HOME}/gcloud-service-key.json
+gcloud --quiet config set project flutter-infra
+
+pushd dev/integration_tests/release_smoke_test
+
+../../../bin/flutter build appbundle \
+  --debug \
+  --target "test_adapter/hello_world_test.dart"
+
+pushd android
+
+./gradlew assembleAndroidTest
+
+popd
+
+# Firebase Test Lab tests are currently known to be failing with
+# "Firebase Test Lab infrastructure failure: Error during preprocessing"
+# Remove "|| exit 0" once the failures are resolved
+# https://github.com/flutter/flutter/issues/36501
+
+# Runs on an emulator because it's cheaper.
+gcloud firebase test android run \
+  --type=instrumentation \
+  --app="build/app/outputs/bundle/debug/app.aab" \
+  --test="build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk" \
+  --device="model=Pixel2,version=28,locale=en,orientation=portrait" \
+  --timeout 2m \
+  --results-bucket=gs://flutter_firebase_testlab \
+  --results-dir=release_smoke_test/$GIT_REVISION/$CIRRUS_BUILD_ID || exit 0
+
+popd
+
+# Check logcat for "E/flutter" - if it's there, something's wrong.
+gsutil cp gs://flutter_firebase_testlab/release_smoke_test/$GIT_REVISION/$CIRRUS_BUILD_ID/Pixel2-28-en-portrait/logcat /tmp/logcat
+! grep "E/flutter" /tmp/logcat || false
+grep "I/flutter" /tmp/logcat
diff --git a/dev/integration_tests/release_smoke_test/android/app/build.gradle b/dev/integration_tests/release_smoke_test/android/app/build.gradle
index 2b4766d..13b8b3c 100644
--- a/dev/integration_tests/release_smoke_test/android/app/build.gradle
+++ b/dev/integration_tests/release_smoke_test/android/app/build.gradle
@@ -56,6 +56,6 @@
 
 dependencies {
     testImplementation 'junit:junit:4.12'
-    androidTestImplementation 'androidx.test:runner:1.1.1'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+    androidTestImplementation 'androidx.test:runner:1.2.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
 }
diff --git a/dev/integration_tests/release_smoke_test/android/app/src/androidTest/java/com/example/release_smoke_test/MainActivityTest.java b/dev/integration_tests/release_smoke_test/android/app/src/androidTest/java/com/example/release_smoke_test/MainActivityTest.java
new file mode 100644
index 0000000..332d110
--- /dev/null
+++ b/dev/integration_tests/release_smoke_test/android/app/src/androidTest/java/com/example/release_smoke_test/MainActivityTest.java
@@ -0,0 +1,12 @@
+package com.example.release_smoke_test;
+
+import androidx.test.rule.ActivityTestRule;
+import dev.flutter.plugins.instrumentationadapter.FlutterRunner;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(FlutterRunner.class)
+public class MainActivityTest {
+  @Rule
+  public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
+}
diff --git a/dev/integration_tests/release_smoke_test/android/gradle.properties b/dev/integration_tests/release_smoke_test/android/gradle.properties
index a673820..b1e161a 100644
--- a/dev/integration_tests/release_smoke_test/android/gradle.properties
+++ b/dev/integration_tests/release_smoke_test/android/gradle.properties
@@ -2,3 +2,5 @@
 android.useAndroidX=true
 android.enableJetifier=true
 android.enableR8=true
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/dev/integration_tests/release_smoke_test/ios/Flutter/Debug.xcconfig b/dev/integration_tests/release_smoke_test/ios/Flutter/Debug.xcconfig
index 592ceee..e8efba1 100644
--- a/dev/integration_tests/release_smoke_test/ios/Flutter/Debug.xcconfig
+++ b/dev/integration_tests/release_smoke_test/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "Generated.xcconfig"
diff --git a/dev/integration_tests/release_smoke_test/ios/Flutter/Release.xcconfig b/dev/integration_tests/release_smoke_test/ios/Flutter/Release.xcconfig
index 592ceee..399e934 100644
--- a/dev/integration_tests/release_smoke_test/ios/Flutter/Release.xcconfig
+++ b/dev/integration_tests/release_smoke_test/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "Generated.xcconfig"
diff --git a/dev/integration_tests/release_smoke_test/ios/Podfile b/dev/integration_tests/release_smoke_test/ios/Podfile
new file mode 100644
index 0000000..1de1efc
--- /dev/null
+++ b/dev/integration_tests/release_smoke_test/ios/Podfile
@@ -0,0 +1,75 @@
+# Using a CDN with CocoaPods 1.7.2 or later can save a lot of time on pod installation, but it's experimental rather than the default.
+# source 'https://cdn.cocoapods.org/'
+
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def parse_KV_file(file, separator='=')
+  file_abs_path = File.expand_path(file)
+  if !File.exists? file_abs_path
+    return [];
+  end
+  pods_ary = []
+  skip_line_start_symbols = ["#", "/"]
+  File.foreach(file_abs_path) { |line|
+      next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
+      plugin = line.split(pattern=separator)
+      if plugin.length == 2
+        podname = plugin[0].strip()
+        path = plugin[1].strip()
+        podpath = File.expand_path("#{path}", file_abs_path)
+        pods_ary.push({:name => podname, :path => podpath});
+      else
+        puts "Invalid plugin specification: #{line}"
+      end
+  }
+  return pods_ary
+end
+
+target 'Runner' do
+  # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
+  # referring to absolute paths on developers' machines.
+  system('rm -rf .symlinks')
+  system('mkdir -p .symlinks/plugins')
+
+  # Flutter Pods
+  generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
+  if generated_xcode_build_settings.empty?
+    puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first."
+  end
+  generated_xcode_build_settings.map { |p|
+    if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
+      symlink = File.join('.symlinks', 'flutter')
+      File.symlink(File.dirname(p[:path]), symlink)
+      pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
+    end
+  }
+
+  # Plugin Pods
+  plugin_pods = parse_KV_file('../.flutter-plugins')
+  plugin_pods.map { |p|
+    symlink = File.join('.symlinks', 'plugins', p[:name])
+    File.symlink(p[:path], symlink)
+    pod p[:name], :path => File.join(symlink, 'ios')
+  }
+end
+
+# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
+install! 'cocoapods', :disable_input_output_paths => true
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    target.build_configurations.each do |config|
+      config.build_settings['ENABLE_BITCODE'] = 'NO'
+    end
+  end
+end
diff --git a/dev/integration_tests/release_smoke_test/pubspec.yaml b/dev/integration_tests/release_smoke_test/pubspec.yaml
index f35ab49..35433cd 100644
--- a/dev/integration_tests/release_smoke_test/pubspec.yaml
+++ b/dev/integration_tests/release_smoke_test/pubspec.yaml
@@ -17,6 +17,8 @@
   flutter_test:
     sdk: flutter
 
+  instrumentation_adapter: 0.1.3
+
   archive: 2.0.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   async: 2.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -38,4 +40,4 @@
   test_api: 0.2.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
   xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
 
-# PUBSPEC CHECKSUM: 5758
+# PUBSPEC CHECKSUM: 8361
diff --git a/dev/integration_tests/release_smoke_test/test_adapter/hello_world_test.dart b/dev/integration_tests/release_smoke_test/test_adapter/hello_world_test.dart
new file mode 100644
index 0000000..80690ae
--- /dev/null
+++ b/dev/integration_tests/release_smoke_test/test_adapter/hello_world_test.dart
@@ -0,0 +1,19 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:release_smoke_test/main.dart' as smoke;
+import 'package:instrumentation_adapter/instrumentation_adapter.dart';
+
+void main() {
+  InstrumentationAdapterFlutterBinding.ensureInitialized();
+
+  testWidgets('Hello world smoke test', (WidgetTester tester) async {
+    smoke.main(); // builds the app and schedules a frame but doesn't trigger one
+
+    await tester.pump(); // triggers a frame
+
+    expect(find.text('Hello, world!'), findsOneWidget);
+  });
+}