| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // @dart = 2.8 |
| |
| import 'package:file/memory.dart'; |
| import 'package:file_testing/file_testing.dart'; |
| import 'package:flutter_tools/src/android/gradle_errors.dart'; |
| import 'package:flutter_tools/src/android/gradle_utils.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/platform.dart'; |
| import 'package:flutter_tools/src/globals_null_migrated.dart' as globals; |
| import 'package:flutter_tools/src/project.dart'; |
| import 'package:flutter_tools/src/reporting/reporting.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| |
| void main() { |
| group('gradleErrors', () { |
| testWithoutContext('list of errors', () { |
| // If you added a new Gradle error, please update this test. |
| expect(gradleErrors, |
| equals(<GradleHandledError>[ |
| licenseNotAcceptedHandler, |
| networkErrorHandler, |
| permissionDeniedErrorHandler, |
| flavorUndefinedHandler, |
| r8FailureHandler, |
| androidXFailureHandler, |
| minSdkVersion, |
| transformInputIssue, |
| lockFileDepMissing, |
| ]) |
| ); |
| }); |
| }); |
| |
| group('network errors', () { |
| testUsingContext('retries and deletes zip if gradle fails to unzip', () async { |
| globals.fs.file('foo/.gradle/fizz.zip').createSync(recursive: true); |
| const String errorMessage = r''' |
| Exception in thread "main" java.util.zip.ZipException: error in opening zip file |
| at java.util.zip.ZipFile.open(Native Method) |
| at java.util.zip.ZipFile.(ZipFile.java:225) |
| at java.util.zip.ZipFile.(ZipFile.java:155) |
| at java.util.zip.ZipFile.(ZipFile.java:169) |
| at org.gradle.wrapper.Install.unzip(Install.java:214) |
| at org.gradle.wrapper.Install.access$600(Install.java:27) |
| at org.gradle.wrapper.Install$1.call(Install.java:74) |
| at org.gradle.wrapper.Install$1.call(Install.java:48) |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61) |
| [!] Gradle threw an error while trying to update itself. Retrying the update... |
| Exception in thread "main" java.util.zip.ZipException: error in opening zip file |
| at java.util.zip.ZipFile.open(Native Method) |
| at java.util.zip.ZipFile.(ZipFile.java:225) |
| at java.util.zip.ZipFile.(ZipFile.java:155) |
| at java.util.zip.ZipFile.(ZipFile.java:169) |
| at org.gradle.wrapper.Install.unzip(Install.java:214) |
| at org.gradle.wrapper.Install.access$600(Install.java:27) |
| at org.gradle.wrapper.Install$1.call(Install.java:74) |
| at org.gradle.wrapper.Install$1.call(Install.java:48) |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61) |
| '''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| expect(globals.fs.file('foo/.gradle/fizz.zip'), isNot(exists)); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}), |
| }); |
| |
| testUsingContext('retries if gradle fails while downloading', () async { |
| const String errorMessage = r''' |
| Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle.org/distributions/gradle-4.1.1-all.zip |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1872) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474) |
| at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254) |
| at org.gradle.wrapper.Download.downloadInternal(Download.java:58) |
| at org.gradle.wrapper.Download.download(Download.java:44) |
| at org.gradle.wrapper.Install$1.call(Install.java:61) |
| at org.gradle.wrapper.Install$1.call(Install.java:48) |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| |
| expect(testLogger.errorText, |
| contains( |
| 'Gradle threw an error while downloading artifacts from the network. ' |
| 'Retrying to download...' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('retries if gradle fails downloading with proxy error', () async { |
| const String errorMessage = r''' |
| Exception in thread "main" java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 400 Bad Request" |
| at sun.net.www.protocol.http.HttpURLConnection.doTunneling(HttpURLConnection.java:2124) |
| at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:183) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1546) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474) |
| at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254) |
| at org.gradle.wrapper.Download.downloadInternal(Download.java:58) |
| at org.gradle.wrapper.Download.download(Download.java:44) |
| at org.gradle.wrapper.Install$1.call(Install.java:61) |
| at org.gradle.wrapper.Install$1.call(Install.java:48) |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| |
| expect(testLogger.errorText, |
| contains( |
| 'Gradle threw an error while downloading artifacts from the network. ' |
| 'Retrying to download...' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('retries if gradle times out waiting for exclusive access to zip', () async { |
| const String errorMessage = ''' |
| Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached waiting for exclusive access to file: /User/documents/gradle-5.6.2-all.zip |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:61) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| |
| expect(testLogger.errorText, |
| contains( |
| 'Gradle threw an error while downloading artifacts from the network. ' |
| 'Retrying to download...' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('retries if remote host closes connection', () async { |
| const String errorMessage = r''' |
| Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip |
| Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake |
| at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:994) |
| at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367) |
| at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395) |
| at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379) |
| at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559) |
| at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) |
| at sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2729) |
| at sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2641) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1824) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492) |
| at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263) |
| at org.gradle.wrapper.Download.downloadInternal(Download.java:58) |
| at org.gradle.wrapper.Download.download(Download.java:44) |
| at org.gradle.wrapper.Install$1.call(Install.java:61) |
| at org.gradle.wrapper.Install$1.call(Install.java:48) |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| |
| expect(testLogger.errorText, |
| contains( |
| 'Gradle threw an error while downloading artifacts from the network. ' |
| 'Retrying to download...' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('retries if file opening fails', () async { |
| const String errorMessage = r''' |
| Downloading https://services.gradle.org/distributions/gradle-3.5.0-all.zip |
| Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle-dn.com/distributions/gradle-3.5.0-all.zip |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1890) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492) |
| at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263) |
| at org.gradle.wrapper.Download.downloadInternal(Download.java:58) |
| at org.gradle.wrapper.Download.download(Download.java:44) |
| at org.gradle.wrapper.Install$1.call(Install.java:61) |
| at org.gradle.wrapper.Install$1.call(Install.java:48) |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| |
| expect(testLogger.errorText, |
| contains( |
| 'Gradle threw an error while downloading artifacts from the network. ' |
| 'Retrying to download...' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('retries if the connection is reset', () async { |
| const String errorMessage = r''' |
| Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip |
| Exception in thread "main" java.net.SocketException: Connection reset |
| at java.net.SocketInputStream.read(SocketInputStream.java:210) |
| at java.net.SocketInputStream.read(SocketInputStream.java:141) |
| at sun.security.ssl.InputRecord.readFully(InputRecord.java:465) |
| at sun.security.ssl.InputRecord.readV3Record(InputRecord.java:593) |
| at sun.security.ssl.InputRecord.read(InputRecord.java:532) |
| at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975) |
| at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367) |
| at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395) |
| at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379) |
| at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559) |
| at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564) |
| at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492) |
| at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263) |
| at org.gradle.wrapper.Download.downloadInternal(Download.java:58) |
| at org.gradle.wrapper.Download.download(Download.java:44) |
| at org.gradle.wrapper.Install$1.call(Install.java:61) |
| at org.gradle.wrapper.Install$1.call(Install.java:48) |
| at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65) |
| at org.gradle.wrapper.Install.createDist(Install.java:48) |
| at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) |
| at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| |
| expect(testLogger.errorText, |
| contains( |
| 'Gradle threw an error while downloading artifacts from the network. ' |
| 'Retrying to download...' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| |
| testUsingContext('retries if Gradle could not get a resource', () async { |
| const String errorMessage = ''' |
| A problem occurred configuring root project 'android'. |
| > Could not resolve all artifacts for configuration ':classpath'. |
| > Could not resolve net.sf.proguard:proguard-gradle:6.0.3. |
| Required by: |
| project : > com.android.tools.build:gradle:3.3.0 |
| > Could not resolve net.sf.proguard:proguard-gradle:6.0.3. |
| > Could not parse POM https://jcenter.bintray.com/net/sf/proguard/proguard-gradle/6.0.3/proguard-gradle-6.0.3.pom |
| > Could not resolve net.sf.proguard:proguard-parent:6.0.3. |
| > Could not resolve net.sf.proguard:proguard-parent:6.0.3. |
| > Could not get resource 'https://jcenter.bintray.com/net/sf/proguard/proguard-parent/6.0.3/proguard-parent-6.0.3.pom'. |
| > Could not GET 'https://jcenter.bintray.com/net/sf/proguard/proguard-parent/6.0.3/proguard-parent-6.0.3.pom'. Received status code 504 from server: Gateway Time-out'''; |
| |
| expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); |
| expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); |
| |
| expect(testLogger.errorText, |
| contains( |
| 'Gradle threw an error while downloading artifacts from the network. ' |
| 'Retrying to download...' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| }); |
| }); |
| |
| group('permission errors', () { |
| testUsingContext('throws toolExit if gradle is missing execute permissions', () async { |
| const String errorMessage = ''' |
| Permission denied |
| Command: /home/android/gradlew assembleRelease |
| '''; |
| expect(formatTestErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue); |
| expect(await permissionDeniedErrorHandler.handler(), equals(GradleBuildStatus.exit)); |
| |
| expect( |
| testLogger.statusText, |
| contains('Gradle does not have execution permission.'), |
| ); |
| expect( |
| testLogger.statusText, |
| contains( |
| 'You should change the ownership of the project directory to your user, ' |
| 'or move the project to a directory with execute permissions.' |
| ) |
| ); |
| }); |
| }); |
| |
| group('AndroidX', () { |
| final TestUsage testUsage = TestUsage(); |
| |
| testWithoutContext('pattern', () { |
| expect(androidXFailureHandler.test( |
| 'AAPT: error: resource android:attr/fontVariationSettings not found.' |
| ), isTrue); |
| |
| expect(androidXFailureHandler.test( |
| 'AAPT: error: resource android:attr/ttcIndex not found.' |
| ), isTrue); |
| |
| expect(androidXFailureHandler.test( |
| 'error: package android.support.annotation does not exist' |
| ), isTrue); |
| |
| expect(androidXFailureHandler.test( |
| 'import android.support.annotation.NonNull;' |
| ), isTrue); |
| |
| expect(androidXFailureHandler.test( |
| 'import androidx.annotation.NonNull;' |
| ), isTrue); |
| |
| expect(androidXFailureHandler.test( |
| 'Daemon: AAPT2 aapt2-3.2.1-4818971-linux Daemon #0' |
| ), isTrue); |
| }); |
| |
| testUsingContext('handler - no plugins', () async { |
| final GradleBuildStatus status = await androidXFailureHandler |
| .handler(line: '', project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory)); |
| |
| expect(testUsage.events, contains( |
| TestUsageEvent( |
| 'build', |
| 'gradle', |
| label: 'gradle-android-x-failure', |
| parameters: CustomDimensions.fromMap(<String, String>{ |
| 'cd43': 'app-not-using-plugins', |
| }), |
| ), |
| )); |
| |
| expect(status, equals(GradleBuildStatus.exit)); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| |
| testUsingContext('handler - plugins and no AndroidX', () async { |
| globals.fs.file('.flutter-plugins').createSync(recursive: true); |
| |
| final GradleBuildStatus status = await androidXFailureHandler |
| .handler( |
| line: '', |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| usesAndroidX: false, |
| ); |
| |
| expect(testLogger.statusText, |
| contains( |
| 'AndroidX incompatibilities may have caused this build to fail. ' |
| 'Please migrate your app to AndroidX. See https://goo.gl/CP92wY .' |
| ) |
| ); |
| |
| expect(testUsage.events, contains( |
| TestUsageEvent( |
| 'build', |
| 'gradle', |
| label: 'gradle-android-x-failure', |
| parameters: CustomDimensions.fromMap(<String, String>{ |
| 'cd43': 'app-not-using-androidx', |
| }), |
| ), |
| )); |
| |
| expect(status, equals(GradleBuildStatus.exit)); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| |
| testUsingContext('handler - plugins, AndroidX, and AAR', () async { |
| globals.fs.file('.flutter-plugins').createSync(recursive: true); |
| |
| final GradleBuildStatus status = await androidXFailureHandler.handler( |
| line: '', |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| usesAndroidX: true, |
| shouldBuildPluginAsAar: true, |
| ); |
| |
| expect(testUsage.events, contains( |
| TestUsageEvent( |
| 'build', |
| 'gradle', |
| label: 'gradle-android-x-failure', |
| parameters: CustomDimensions.fromMap(<String, String>{ |
| 'cd43': 'using-jetifier', |
| }), |
| ), |
| )); |
| |
| expect(status, equals(GradleBuildStatus.exit)); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| |
| testUsingContext('handler - plugins, AndroidX, and no AAR', () async { |
| globals.fs.file('.flutter-plugins').createSync(recursive: true); |
| |
| final GradleBuildStatus status = await androidXFailureHandler.handler( |
| line: '', |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| usesAndroidX: true, |
| shouldBuildPluginAsAar: false, |
| ); |
| |
| expect(testLogger.statusText, |
| contains( |
| 'The build failed likely due to AndroidX incompatibilities in a plugin. ' |
| 'The tool is about to try using Jetifier to solve the incompatibility.' |
| ) |
| ); |
| |
| expect(testUsage.events, contains( |
| TestUsageEvent( |
| 'build', |
| 'gradle', |
| label: 'gradle-android-x-failure', |
| parameters: CustomDimensions.fromMap(<String, String>{ |
| 'cd43': 'not-using-jetifier', |
| }), |
| ), |
| )); |
| expect(status, equals(GradleBuildStatus.retryWithAarPlugins)); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.any(), |
| Usage: () => testUsage, |
| }); |
| }); |
| |
| group('permission errors', () { |
| testUsingContext('pattern', () async { |
| const String errorMessage = ''' |
| Permission denied |
| Command: /home/android/gradlew assembleRelease |
| '''; |
| expect(formatTestErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue); |
| }); |
| |
| testUsingContext('handler', () async { |
| expect(await permissionDeniedErrorHandler.handler(), equals(GradleBuildStatus.exit)); |
| |
| expect( |
| testLogger.statusText, |
| contains('Gradle does not have execution permission.'), |
| ); |
| expect( |
| testLogger.statusText, |
| contains( |
| 'You should change the ownership of the project directory to your user, ' |
| 'or move the project to a directory with execute permissions.' |
| ) |
| ); |
| }); |
| }); |
| |
| group('license not accepted', () { |
| testWithoutContext('pattern', () { |
| expect( |
| licenseNotAcceptedHandler.test( |
| 'You have not accepted the license agreements of the following SDK components' |
| ), |
| isTrue, |
| ); |
| }); |
| |
| testUsingContext('handler', () async { |
| await licenseNotAcceptedHandler.handler( |
| line: 'You have not accepted the license agreements of the following SDK components: [foo, bar]', |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| contains( |
| 'Unable to download needed Android SDK components, as the ' |
| 'following licenses have not been accepted:\n' |
| 'foo, bar\n\n' |
| 'To resolve this, please run the following command in a Terminal:\n' |
| 'flutter doctor --android-licenses' |
| ) |
| ); |
| }); |
| }); |
| |
| group('flavor undefined', () { |
| FakeProcessManager fakeProcessManager; |
| |
| setUp(() { |
| fakeProcessManager = FakeProcessManager.empty(); |
| }); |
| |
| testWithoutContext('pattern', () { |
| expect( |
| flavorUndefinedHandler.test( |
| 'Task assembleFooRelease not found in root project.' |
| ), |
| isTrue, |
| ); |
| expect( |
| flavorUndefinedHandler.test( |
| 'Task assembleBarRelease not found in root project.' |
| ), |
| isTrue, |
| ); |
| expect( |
| flavorUndefinedHandler.test( |
| 'Task assembleBar not found in root project.' |
| ), |
| isTrue, |
| ); |
| expect( |
| flavorUndefinedHandler.test( |
| 'Task assembleBar_foo not found in root project.' |
| ), |
| isTrue, |
| ); |
| }); |
| |
| testUsingContext('handler - with flavor', () async { |
| fakeProcessManager.addCommand(const FakeCommand( |
| command: <String>[ |
| 'gradlew', |
| 'app:tasks' , |
| '--all', |
| '--console=auto', |
| ], |
| stdout: ''' |
| assembleRelease |
| assembleFlavor1 |
| assembleFlavor1Release |
| assembleFlavor_2 |
| assembleFlavor_2Release |
| assembleDebug |
| assembleProfile |
| assembles |
| assembleFooTest |
| ''', |
| )); |
| |
| await flavorUndefinedHandler.handler( |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| contains( |
| 'Gradle project does not define a task suitable ' |
| 'for the requested build.' |
| ) |
| ); |
| expect( |
| testLogger.statusText, |
| contains( |
| 'The android/app/build.gradle file defines product ' |
| 'flavors: flavor1, flavor_2 ' |
| 'You must specify a --flavor option to select one of them.' |
| ) |
| ); |
| expect(fakeProcessManager.hasRemainingExpectations, isFalse); |
| }, overrides: <Type, Generator>{ |
| GradleUtils: () => FakeGradleUtils(), |
| Platform: () => fakePlatform('android'), |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| }); |
| |
| testUsingContext('handler - without flavor', () async { |
| fakeProcessManager.addCommand(const FakeCommand( |
| command: <String>[ |
| 'gradlew', |
| 'app:tasks' , |
| '--all', |
| '--console=auto', |
| ], |
| stdout: ''' |
| assembleRelease |
| assembleDebug |
| assembleProfile |
| ''', |
| )); |
| |
| await flavorUndefinedHandler.handler( |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| contains( |
| 'Gradle project does not define a task suitable ' |
| 'for the requested build.' |
| ) |
| ); |
| expect( |
| testLogger.statusText, |
| contains( |
| 'The android/app/build.gradle file does not define any custom product flavors. ' |
| 'You cannot use the --flavor option.' |
| ) |
| ); |
| expect(fakeProcessManager.hasRemainingExpectations, isFalse); |
| }, overrides: <Type, Generator>{ |
| GradleUtils: () => FakeGradleUtils(), |
| Platform: () => fakePlatform('android'), |
| ProcessManager: () => fakeProcessManager, |
| FileSystem: () => MemoryFileSystem.test(), |
| }); |
| }); |
| |
| group('higher minSdkVersion', () { |
| const String stdoutLine = 'uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:webview_flutter] /tmp/cirrus-ci-build/all_plugins/build/webview_flutter/intermediates/library_manifest/release/AndroidManifest.xml as the library might be using APIs not available in 16'; |
| |
| testWithoutContext('pattern', () { |
| expect( |
| minSdkVersion.test(stdoutLine), |
| isTrue, |
| ); |
| }); |
| |
| testUsingContext('suggestion', () async { |
| await minSdkVersion.handler( |
| line: stdoutLine, |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| contains( |
| '\n' |
| 'The plugin webview_flutter requires a higher Android SDK version.\n' |
| 'Fix this issue by adding the following to the file /android/app/build.gradle:\n' |
| 'android {\n' |
| ' defaultConfig {\n' |
| ' minSdkVersion 19\n' |
| ' }\n' |
| '}\n' |
| '\n' |
| "Note that your app won't be available to users running Android SDKs below 19.\n" |
| 'Alternatively, try to find a version of this plugin that supports these lower versions of the Android SDK.\n' |
| '' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| GradleUtils: () => FakeGradleUtils(), |
| Platform: () => fakePlatform('android'), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.empty(), |
| }); |
| }); |
| |
| // https://issuetracker.google.com/issues/141126614 |
| group('transform input issue', () { |
| testWithoutContext('pattern', () { |
| expect( |
| transformInputIssue.test( |
| 'https://issuetracker.google.com/issues/158753935' |
| ), |
| isTrue, |
| ); |
| }); |
| |
| testUsingContext('suggestion', () async { |
| await transformInputIssue.handler( |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| contains( |
| '\n' |
| 'This issue appears to be https://github.com/flutter/flutter/issues/58247.\n' |
| 'Fix this issue by adding the following to the file /android/app/build.gradle:\n' |
| 'android {\n' |
| ' lintOptions {\n' |
| ' checkReleaseBuilds false\n' |
| ' }\n' |
| '}\n' |
| '' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| GradleUtils: () => FakeGradleUtils(), |
| Platform: () => fakePlatform('android'), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.empty(), |
| }); |
| }); |
| |
| group('Dependency mismatch', () { |
| testWithoutContext('pattern', () { |
| expect( |
| lockFileDepMissing.test(''' |
| * What went wrong: |
| Execution failed for task ':app:generateDebugFeatureTransitiveDeps'. |
| > Could not resolve all artifacts for configuration ':app:debugRuntimeClasspath'. |
| > Resolved 'androidx.lifecycle:lifecycle-common:2.2.0' which is not part of the dependency lock state |
| > Resolved 'androidx.customview:customview:1.0.0' which is not part of the dependency lock state''' |
| ), |
| isTrue, |
| ); |
| }); |
| |
| testUsingContext('suggestion', () async { |
| await lockFileDepMissing.handler( |
| project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), |
| ); |
| |
| expect( |
| testLogger.statusText, |
| contains( |
| '\n' |
| 'You need to update the lockfile, or disable Gradle dependency locking.\n' |
| 'To regenerate the lockfiles run: `./gradlew :generateLockfiles` in /android/build.gradle\n' |
| 'To remove dependency locking, remove the `dependencyLocking` from /android/build.gradle\n' |
| '\n' |
| '' |
| ) |
| ); |
| }, overrides: <Type, Generator>{ |
| GradleUtils: () => FakeGradleUtils(), |
| Platform: () => fakePlatform('android'), |
| FileSystem: () => MemoryFileSystem.test(), |
| ProcessManager: () => FakeProcessManager.empty(), |
| }); |
| }); |
| } |
| |
| bool formatTestErrorMessage(String errorMessage, GradleHandledError error) { |
| return errorMessage |
| .split('\n') |
| .any((String line) => error.test(line)); |
| } |
| |
| Platform fakePlatform(String name) { |
| return FakePlatform( |
| environment: <String, String>{ |
| 'HOME': '/', |
| }, |
| operatingSystem: name, |
| ); |
| } |
| |
| class FakeGradleUtils extends GradleUtils { |
| @override |
| String getExecutable(FlutterProject project) { |
| return 'gradlew'; |
| } |
| } |