Cocoon getFlutterBranches() (#686)
diff --git a/app_flutter/lib/build_dashboard_page.dart b/app_flutter/lib/build_dashboard_page.dart
index eb8d644..34b9bd6 100644
--- a/app_flutter/lib/build_dashboard_page.dart
+++ b/app_flutter/lib/build_dashboard_page.dart
@@ -37,7 +37,7 @@
void initState() {
super.initState();
- widget.buildState.startFetchingBuildStateUpdates();
+ widget.buildState.startFetchingUpdates();
widget.buildState.errors.addListener(_showErrorSnackbar);
}
diff --git a/app_flutter/lib/service/appengine_cocoon.dart b/app_flutter/lib/service/appengine_cocoon.dart
index 8b9635b..80ab52b 100644
--- a/app_flutter/lib/service/appengine_cocoon.dart
+++ b/app_flutter/lib/service/appengine_cocoon.dart
@@ -116,6 +116,28 @@
}
@override
+ Future<CocoonResponse<List<String>>> fetchFlutterBranches() async {
+ final String getBranchesUrl = _apiEndpoint('/api/public/get-branches');
+
+ /// This endpoint returns JSON {"Branches": List<String>}
+ final http.Response response = await _client.get(getBranchesUrl);
+
+ if (response.statusCode != HttpStatus.ok) {
+ print(response.body);
+ return CocoonResponse<List<String>>()
+ ..error = '/api/public/get-branches returned ${response.statusCode}';
+ }
+
+ try {
+ final Map<String, dynamic> jsonResponse = jsonDecode(response.body);
+ final List<String> branches = jsonResponse['Branches'].cast<String>();
+ return CocoonResponse<List<String>>()..data = branches;
+ } catch (error) {
+ return CocoonResponse<List<String>>()..error = error.toString();
+ }
+ }
+
+ @override
Future<bool> rerunTask(Task task, String idToken) async {
assert(idToken != null);
final String postResetTaskUrl = _apiEndpoint('/api/reset-devicelab-task');
diff --git a/app_flutter/lib/service/cocoon.dart b/app_flutter/lib/service/cocoon.dart
index c5043ac..d65337a 100644
--- a/app_flutter/lib/service/cocoon.dart
+++ b/app_flutter/lib/service/cocoon.dart
@@ -39,6 +39,9 @@
/// Get the current Flutter infra agent statuses.
Future<CocoonResponse<List<Agent>>> fetchAgentStatuses();
+ /// Get the current list of version branches in flutter/flutter.
+ Future<CocoonResponse<List<String>>> fetchFlutterBranches();
+
/// Send rerun [Task] command to devicelab.
///
/// Will not rerun tasks that are outside of devicelab.
diff --git a/app_flutter/lib/service/fake_cocoon.dart b/app_flutter/lib/service/fake_cocoon.dart
index 8dd4d16..3b17b32 100644
--- a/app_flutter/lib/service/fake_cocoon.dart
+++ b/app_flutter/lib/service/fake_cocoon.dart
@@ -37,6 +37,12 @@
}
@override
+ Future<CocoonResponse<List<String>>> fetchFlutterBranches() async {
+ return CocoonResponse<List<String>>()
+ ..data = <String>['master', 'dev', 'beta', 'stable'];
+ }
+
+ @override
Future<bool> rerunTask(Task task, String accessToken) async {
return false;
}
diff --git a/app_flutter/lib/state/flutter_build.dart b/app_flutter/lib/state/flutter_build.dart
index 0f79019..f4f6484 100644
--- a/app_flutter/lib/state/flutter_build.dart
+++ b/app_flutter/lib/state/flutter_build.dart
@@ -47,6 +47,10 @@
bool _isTreeBuilding;
bool get isTreeBuilding => _isTreeBuilding;
+ /// Git branches from flutter/flutter for managing Flutter releases.
+ List<String> _branches = <String>['master'];
+ List<String> get branches => _branches;
+
/// A [ChangeNotifer] for knowing when errors occur that relate to this [FlutterBuildState].
FlutterBuildStateErrors errors = FlutterBuildStateErrors();
@@ -58,8 +62,12 @@
static const String errorMessageFetchingTreeStatus =
'An error occured fetching tree status from Cocoon';
+ @visibleForTesting
+ static const String errorMessageFetchingBranches =
+ 'An error occured fetching branches from flutter/flutter on Cocoon.';
+
/// Start a fixed interval loop that fetches build state updates based on [refreshRate].
- Future<void> startFetchingBuildStateUpdates() async {
+ Future<void> startFetchingUpdates() async {
if (refreshTimer != null) {
// There's already an update loop, no need to make another.
return;
@@ -68,6 +76,9 @@
/// [Timer.periodic] does not necessarily run at the start of the timer.
_fetchBuildStatusUpdate();
+ _fetchFlutterBranches()
+ .then((List<String> branchResponse) => _branches = branchResponse);
+
refreshTimer =
Timer.periodic(refreshRate, (_) => _fetchBuildStatusUpdate());
}
@@ -102,6 +113,20 @@
]);
}
+ Future<List<String>> _fetchFlutterBranches() async {
+ return _cocoonService
+ .fetchFlutterBranches()
+ .then((CocoonResponse<List<String>> response) {
+ if (response.error != null) {
+ print(response.error);
+ errors.message = errorMessageFetchingBranches;
+ errors.notifyListeners();
+ }
+
+ return response.data;
+ });
+ }
+
/// Handle merging status updates with the current data in [statuses].
///
/// [recentStatuses] is expected to be sorted from newest commit to oldest
diff --git a/app_flutter/test/service/appengine_cocoon_test.dart b/app_flutter/test/service/appengine_cocoon_test.dart
index e87d94c..2e31fc6 100644
--- a/app_flutter/test/service/appengine_cocoon_test.dart
+++ b/app_flutter/test/service/appengine_cocoon_test.dart
@@ -89,6 +89,15 @@
}
''';
+const String jsonGetBranchesResponse = '''
+ {
+ "Branches": [
+ "master",
+ "flutter-0.0-candidate.1"
+ ]
+ }
+''';
+
const String jsonBuildStatusTrueResponse = '''
{
"AnticipatedBuildStatus": "Succeeded"
@@ -458,6 +467,67 @@
});
});
+ group('AppEngine CocoonService fetchFlutterBranches', () {
+ AppEngineCocoonService service;
+
+ setUp(() async {
+ service =
+ AppEngineCocoonService(client: MockClient((Request request) async {
+ return Response(jsonGetBranchesResponse, 200);
+ }));
+ });
+
+ test('should return CocoonResponse<List<String>>', () {
+ expect(service.fetchFlutterBranches(),
+ const TypeMatcher<Future<CocoonResponse<List<String>>>>());
+ });
+
+ test('data should be expected list of branches', () async {
+ final CocoonResponse<List<String>> branches =
+ await service.fetchFlutterBranches();
+
+ expect(branches.data, <String>[
+ 'master',
+ 'flutter-0.0-candidate.1',
+ ]);
+ });
+
+ /// This requires a separate test run on the web platform.
+ test('should query correct endpoint whether web or mobile', () async {
+ final Client mockClient = MockHttpClient();
+ when(mockClient.get(any))
+ .thenAnswer((_) => Future<Response>.value(Response('', 200)));
+ service = AppEngineCocoonService(client: mockClient);
+
+ await service.fetchFlutterBranches();
+
+ if (kIsWeb) {
+ verify(mockClient.get('/api/public/get-branches'));
+ } else {
+ verify(mockClient.get(
+ 'https://flutter-dashboard.appspot.com/api/public/get-branches'));
+ }
+ });
+
+ test('should have error if given non-200 response', () async {
+ service = AppEngineCocoonService(
+ client: MockClient((Request request) async => Response('', 404)));
+
+ final CocoonResponse<List<String>> response =
+ await service.fetchFlutterBranches();
+ expect(response.error, isNotNull);
+ });
+
+ test('should have error if given bad response', () async {
+ service = AppEngineCocoonService(
+ client: MockClient((Request request) async => Response('bad', 200)));
+
+ final CocoonResponse<List<String>> response =
+ await service.fetchFlutterBranches();
+ expect(response.error, isNotNull);
+ });
+ });
+
group('AppEngine Cocoon Service create agent', () {
AppEngineCocoonService service;
diff --git a/app_flutter/test/state/flutter_build_test.dart b/app_flutter/test/state/flutter_build_test.dart
index 7d560a0..2f5ee73 100644
--- a/app_flutter/test/state/flutter_build_test.dart
+++ b/app_flutter/test/state/flutter_build_test.dart
@@ -36,11 +36,25 @@
when(mockService.fetchTreeBuildStatus()).thenAnswer((_) =>
Future<CocoonResponse<bool>>.value(
CocoonResponse<bool>()..data = true));
+ when(mockService.fetchFlutterBranches()).thenAnswer((_) =>
+ Future<CocoonResponse<List<String>>>.value(
+ CocoonResponse<List<String>>()..data = <String>['master']));
+ });
+
+ testWidgets('start calls fetch branches', (WidgetTester tester) async {
+ buildState.startFetchingUpdates();
+
+ // startFetching immediately starts fetching results
+ verify(mockService.fetchFlutterBranches()).called(1);
+
+ // Tear down fails to cancel the timer
+ await tester.pump(buildState.refreshRate * 2);
+ buildState.dispose();
});
testWidgets('timer should periodically fetch updates',
(WidgetTester tester) async {
- buildState.startFetchingBuildStateUpdates();
+ buildState.startFetchingUpdates();
// startFetching immediately starts fetching results
verify(mockService.fetchCommitStatuses()).called(1);
@@ -56,11 +70,11 @@
testWidgets('multiple start updates should not change the timer',
(WidgetTester tester) async {
- buildState.startFetchingBuildStateUpdates();
+ buildState.startFetchingUpdates();
final Timer refreshTimer = buildState.refreshTimer;
// This second run should not change the refresh timer
- buildState.startFetchingBuildStateUpdates();
+ buildState.startFetchingUpdates();
expect(refreshTimer, equals(buildState.refreshTimer));
@@ -74,7 +88,7 @@
testWidgets('statuses error should not delete previous statuses data',
(WidgetTester tester) async {
- buildState.startFetchingBuildStateUpdates();
+ buildState.startFetchingUpdates();
// Periodic timers don't necessarily run at the same time in each interval.
// We double the refreshRate to gurantee a call would have been made.
@@ -99,7 +113,7 @@
testWidgets(
'build status error should not delete previous build status data',
(WidgetTester tester) async {
- buildState.startFetchingBuildStateUpdates();
+ buildState.startFetchingUpdates();
// Periodic timers don't necessarily run at the same time in each interval.
// We double the refreshRate to gurantee a call would have been made.
@@ -123,7 +137,7 @@
testWidgets('fetch more commit statuses appends',
(WidgetTester tester) async {
- buildState.startFetchingBuildStateUpdates();
+ buildState.startFetchingUpdates();
await untilCalled(mockService.fetchCommitStatuses());
diff --git a/app_flutter/test/utils/fake_flutter_build.dart b/app_flutter/test/utils/fake_flutter_build.dart
index 970342a..b7c7ab3 100644
--- a/app_flutter/test/utils/fake_flutter_build.dart
+++ b/app_flutter/test/utils/fake_flutter_build.dart
@@ -38,7 +38,7 @@
Future<void> signOut() => null;
@override
- Future<void> startFetchingBuildStateUpdates() => null;
+ Future<void> startFetchingUpdates() => null;
@override
List<CommitStatus> statuses = <CommitStatus>[];
@@ -48,4 +48,7 @@
@override
Future<void> fetchMoreCommitStatuses() => null;
+
+ @override
+ List<String> get branches => <String>['master'];
}