1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6import 'dart:convert'; 7import 'dart:io' hide File; 8 9import 'package:args/command_runner.dart'; 10import 'package:file/memory.dart'; 11import 'package:flutter_tools/src/base/file_system.dart'; 12import 'package:flutter_tools/src/cache.dart'; 13import 'package:flutter_tools/src/commands/channel.dart'; 14import 'package:flutter_tools/src/version.dart'; 15import 'package:mockito/mockito.dart'; 16import 'package:process/process.dart'; 17 18import '../src/common.dart'; 19import '../src/context.dart'; 20 21Process createMockProcess({ int exitCode = 0, String stdout = '', String stderr = '' }) { 22 final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(<List<int>>[ 23 utf8.encode(stdout), 24 ]); 25 final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(<List<int>>[ 26 utf8.encode(stderr), 27 ]); 28 final Process process = MockProcess(); 29 30 when(process.stdout).thenAnswer((_) => stdoutStream); 31 when(process.stderr).thenAnswer((_) => stderrStream); 32 when(process.exitCode).thenAnswer((_) => Future<int>.value(exitCode)); 33 return process; 34} 35 36void main() { 37 group('channel', () { 38 final MockProcessManager mockProcessManager = MockProcessManager(); 39 40 setUpAll(() { 41 Cache.disableLocking(); 42 }); 43 44 Future<void> simpleChannelTest(List<String> args) async { 45 final ChannelCommand command = ChannelCommand(); 46 final CommandRunner<void> runner = createTestCommandRunner(command); 47 await runner.run(args); 48 expect(testLogger.errorText, hasLength(0)); 49 // The bots may return an empty list of channels (network hiccup?) 50 // and when run locally the list of branches might be different 51 // so we check for the header text rather than any specific channel name. 52 expect(testLogger.statusText, contains('Flutter channels:')); 53 } 54 55 testUsingContext('list', () async { 56 await simpleChannelTest(<String>['channel']); 57 }); 58 59 testUsingContext('verbose list', () async { 60 await simpleChannelTest(<String>['channel', '-v']); 61 }); 62 63 testUsingContext('removes duplicates', () async { 64 final Process process = createMockProcess( 65 stdout: 'origin/dev\n' 66 'origin/beta\n' 67 'origin/stable\n' 68 'upstream/dev\n' 69 'upstream/beta\n' 70 'upstream/stable\n'); 71 when(mockProcessManager.start( 72 <String>['git', 'branch', '-r'], 73 workingDirectory: anyNamed('workingDirectory'), 74 environment: anyNamed('environment'), 75 )).thenAnswer((_) => Future<Process>.value(process)); 76 77 final ChannelCommand command = ChannelCommand(); 78 final CommandRunner<void> runner = createTestCommandRunner(command); 79 await runner.run(<String>['channel']); 80 81 verify(mockProcessManager.start( 82 <String>['git', 'branch', '-r'], 83 workingDirectory: anyNamed('workingDirectory'), 84 environment: anyNamed('environment'), 85 )).called(1); 86 87 expect(testLogger.errorText, hasLength(0)); 88 89 // format the status text for a simpler assertion. 90 final Iterable<String> rows = testLogger.statusText 91 .split('\n') 92 .map((String line) => line.trim()) 93 .where((String line) => line?.isNotEmpty == true) 94 .skip(1); // remove `Flutter channels:` line 95 96 expect(rows, <String>['dev', 'beta', 'stable']); 97 }, overrides: <Type, Generator>{ 98 ProcessManager: () => mockProcessManager, 99 }); 100 101 testUsingContext('can switch channels', () async { 102 when(mockProcessManager.start( 103 <String>['git', 'fetch'], 104 workingDirectory: anyNamed('workingDirectory'), 105 environment: anyNamed('environment'), 106 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 107 when(mockProcessManager.start( 108 <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], 109 workingDirectory: anyNamed('workingDirectory'), 110 environment: anyNamed('environment'), 111 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 112 when(mockProcessManager.start( 113 <String>['git', 'checkout', 'beta', '--'], 114 workingDirectory: anyNamed('workingDirectory'), 115 environment: anyNamed('environment'), 116 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 117 118 final ChannelCommand command = ChannelCommand(); 119 final CommandRunner<void> runner = createTestCommandRunner(command); 120 await runner.run(<String>['channel', 'beta']); 121 122 verify(mockProcessManager.start( 123 <String>['git', 'fetch'], 124 workingDirectory: anyNamed('workingDirectory'), 125 environment: anyNamed('environment'), 126 )).called(1); 127 verify(mockProcessManager.start( 128 <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], 129 workingDirectory: anyNamed('workingDirectory'), 130 environment: anyNamed('environment'), 131 )).called(1); 132 verify(mockProcessManager.start( 133 <String>['git', 'checkout', 'beta', '--'], 134 workingDirectory: anyNamed('workingDirectory'), 135 environment: anyNamed('environment'), 136 )).called(1); 137 138 expect(testLogger.statusText, contains("Switching to flutter channel 'beta'...")); 139 expect(testLogger.errorText, hasLength(0)); 140 141 when(mockProcessManager.start( 142 <String>['git', 'fetch'], 143 workingDirectory: anyNamed('workingDirectory'), 144 environment: anyNamed('environment'), 145 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 146 when(mockProcessManager.start( 147 <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/stable'], 148 workingDirectory: anyNamed('workingDirectory'), 149 environment: anyNamed('environment'), 150 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 151 when(mockProcessManager.start( 152 <String>['git', 'checkout', 'stable', '--'], 153 workingDirectory: anyNamed('workingDirectory'), 154 environment: anyNamed('environment'), 155 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 156 157 await runner.run(<String>['channel', 'stable']); 158 159 verify(mockProcessManager.start( 160 <String>['git', 'fetch'], 161 workingDirectory: anyNamed('workingDirectory'), 162 environment: anyNamed('environment'), 163 )).called(1); 164 verify(mockProcessManager.start( 165 <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/stable'], 166 workingDirectory: anyNamed('workingDirectory'), 167 environment: anyNamed('environment'), 168 )).called(1); 169 verify(mockProcessManager.start( 170 <String>['git', 'checkout', 'stable', '--'], 171 workingDirectory: anyNamed('workingDirectory'), 172 environment: anyNamed('environment'), 173 )).called(1); 174 }, overrides: <Type, Generator>{ 175 ProcessManager: () => mockProcessManager, 176 FileSystem: () => MemoryFileSystem(), 177 }); 178 179 // This verifies that bug https://github.com/flutter/flutter/issues/21134 180 // doesn't return. 181 testUsingContext('removes version stamp file when switching channels', () async { 182 when(mockProcessManager.start( 183 <String>['git', 'fetch'], 184 workingDirectory: anyNamed('workingDirectory'), 185 environment: anyNamed('environment'), 186 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 187 when(mockProcessManager.start( 188 <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], 189 workingDirectory: anyNamed('workingDirectory'), 190 environment: anyNamed('environment'), 191 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 192 when(mockProcessManager.start( 193 <String>['git', 'checkout', 'beta', '--'], 194 workingDirectory: anyNamed('workingDirectory'), 195 environment: anyNamed('environment'), 196 )).thenAnswer((_) => Future<Process>.value(createMockProcess())); 197 198 final File versionCheckFile = Cache.instance.getStampFileFor( 199 VersionCheckStamp.flutterVersionCheckStampFile, 200 ); 201 202 /// Create a bogus "leftover" version check file to make sure it gets 203 /// removed when the channel changes. The content doesn't matter. 204 versionCheckFile.createSync(recursive: true); 205 versionCheckFile.writeAsStringSync(''' 206 { 207 "lastTimeVersionWasChecked": "2151-08-29 10:17:30.763802", 208 "lastKnownRemoteVersion": "2151-09-26 15:56:19.000Z" 209 } 210 '''); 211 212 final ChannelCommand command = ChannelCommand(); 213 final CommandRunner<void> runner = createTestCommandRunner(command); 214 await runner.run(<String>['channel', 'beta']); 215 216 verify(mockProcessManager.start( 217 <String>['git', 'fetch'], 218 workingDirectory: anyNamed('workingDirectory'), 219 environment: anyNamed('environment'), 220 )).called(1); 221 verify(mockProcessManager.start( 222 <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], 223 workingDirectory: anyNamed('workingDirectory'), 224 environment: anyNamed('environment'), 225 )).called(1); 226 verify(mockProcessManager.start( 227 <String>['git', 'checkout', 'beta', '--'], 228 workingDirectory: anyNamed('workingDirectory'), 229 environment: anyNamed('environment'), 230 )).called(1); 231 232 expect(testLogger.statusText, isNot(contains('A new version of Flutter'))); 233 expect(testLogger.errorText, hasLength(0)); 234 expect(versionCheckFile.existsSync(), isFalse); 235 }, overrides: <Type, Generator>{ 236 ProcessManager: () => mockProcessManager, 237 FileSystem: () => MemoryFileSystem(), 238 }); 239 }); 240} 241 242class MockProcessManager extends Mock implements ProcessManager {} 243 244class MockProcess extends Mock implements Process {} 245