Unverified Commit d6dd832d authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] migrate gradle errors and project test to null safety (#110530)

parent 2f3ba1a3
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart'; import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/android/gradle_errors.dart'; import 'package:flutter_tools/src/android/gradle_errors.dart';
...@@ -13,8 +11,8 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -13,8 +11,8 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -22,6 +20,14 @@ import '../../src/fake_process_manager.dart'; ...@@ -22,6 +20,14 @@ import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart'; import '../../src/fakes.dart';
void main() { void main() {
late FileSystem fileSystem;
late FakeProcessManager processManager;
setUp(() {
fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.empty();
});
group('gradleErrors', () { group('gradleErrors', () {
testWithoutContext('list of errors', () { testWithoutContext('list of errors', () {
// If you added a new Gradle error, please update this test. // If you added a new Gradle error, please update this test.
...@@ -64,7 +70,12 @@ at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) ...@@ -64,7 +70,12 @@ at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -72,8 +83,8 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; ...@@ -72,8 +83,8 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if gradle fails downloading with proxy error', () async { testUsingContext('retries if gradle fails downloading with proxy error', () async {
...@@ -94,7 +105,12 @@ at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128) ...@@ -94,7 +105,12 @@ at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -102,8 +118,8 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; ...@@ -102,8 +118,8 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if gradle times out waiting for exclusive access to zip', () async { testUsingContext('retries if gradle times out waiting for exclusive access to zip', () async {
...@@ -115,7 +131,12 @@ Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached ...@@ -115,7 +131,12 @@ Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -123,8 +144,8 @@ Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached ...@@ -123,8 +144,8 @@ Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if remote host closes connection', () async { testUsingContext('retries if remote host closes connection', () async {
...@@ -152,7 +173,12 @@ Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host clos ...@@ -152,7 +173,12 @@ Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host clos
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -160,8 +186,8 @@ Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host clos ...@@ -160,8 +186,8 @@ Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host clos
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if file opening fails', () async { testUsingContext('retries if file opening fails', () async {
...@@ -181,7 +207,12 @@ Exception in thread "main" java.io.FileNotFoundException: https://downloads.grad ...@@ -181,7 +207,12 @@ Exception in thread "main" java.io.FileNotFoundException: https://downloads.grad
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -189,8 +220,8 @@ Exception in thread "main" java.io.FileNotFoundException: https://downloads.grad ...@@ -189,8 +220,8 @@ Exception in thread "main" java.io.FileNotFoundException: https://downloads.grad
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if the connection is reset', () async { testUsingContext('retries if the connection is reset', () async {
...@@ -221,7 +252,12 @@ Exception in thread "main" java.net.SocketException: Connection reset ...@@ -221,7 +252,12 @@ Exception in thread "main" java.net.SocketException: Connection reset
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''; at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -229,8 +265,8 @@ Exception in thread "main" java.net.SocketException: Connection reset ...@@ -229,8 +265,8 @@ Exception in thread "main" java.net.SocketException: Connection reset
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if Gradle could not get a resource', () async { testUsingContext('retries if Gradle could not get a resource', () async {
...@@ -248,7 +284,12 @@ A problem occurred configuring root project 'android'. ...@@ -248,7 +284,12 @@ A problem occurred configuring root project 'android'.
> 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'''; > 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(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -256,8 +297,8 @@ A problem occurred configuring root project 'android'. ...@@ -256,8 +297,8 @@ A problem occurred configuring root project 'android'.
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if Gradle could not get a resource (non-Gateway)', () async { testUsingContext('retries if Gradle could not get a resource (non-Gateway)', () async {
...@@ -279,7 +320,12 @@ A problem occurred configuring root project 'android'. ...@@ -279,7 +320,12 @@ A problem occurred configuring root project 'android'.
> Remote host closed connection during handshake'''; > Remote host closed connection during handshake''';
expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry)); expect(await networkErrorHandler.handler(
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
usesAndroidX: true,
), equals(GradleBuildStatus.retry));
expect(testLogger.errorText, expect(testLogger.errorText,
contains( contains(
...@@ -287,8 +333,8 @@ A problem occurred configuring root project 'android'. ...@@ -287,8 +333,8 @@ A problem occurred configuring root project 'android'.
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
}); });
...@@ -318,7 +364,7 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -318,7 +364,7 @@ Execution failed for task ':app:mergeDexDebug'.
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html'''; Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: true), equals(GradleBuildStatus.exit)); expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(fileSystem.currentDirectory), multidexEnabled: true, usesAndroidX: true, line: ''), equals(GradleBuildStatus.exit));
expect(testLogger.statusText, expect(testLogger.statusText,
contains( contains(
...@@ -336,8 +382,8 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -336,8 +382,8 @@ Execution failed for task ':app:mergeDexDebug'.
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
testUsingContext('retries if multidex support enabled', () async { testUsingContext('retries if multidex support enabled', () async {
const String errorMessage = r''' const String errorMessage = r'''
...@@ -363,7 +409,7 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -363,7 +409,7 @@ Execution failed for task ':app:mergeDexDebug'.
The number of method references in a .dex file cannot exceed 64K. The number of method references in a .dex file cannot exceed 64K.
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html'''; Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
final File manifest = globals.fs.currentDirectory final File manifest = fileSystem.currentDirectory
.childDirectory('android') .childDirectory('android')
.childDirectory('app') .childDirectory('app')
.childDirectory('src') .childDirectory('src')
...@@ -382,7 +428,7 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -382,7 +428,7 @@ Execution failed for task ':app:mergeDexDebug'.
''', flush: true); ''', flush: true);
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: true), equals(GradleBuildStatus.retry)); expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(fileSystem.currentDirectory), multidexEnabled: true, line: '', usesAndroidX: true), equals(GradleBuildStatus.retry));
expect(testLogger.statusText, expect(testLogger.statusText,
contains( contains(
...@@ -395,8 +441,8 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -395,8 +441,8 @@ Execution failed for task ':app:mergeDexDebug'.
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
AnsiTerminal: () => _TestPromptTerminal('y'), AnsiTerminal: () => _TestPromptTerminal('y'),
}); });
...@@ -424,7 +470,7 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -424,7 +470,7 @@ Execution failed for task ':app:mergeDexDebug'.
The number of method references in a .dex file cannot exceed 64K. The number of method references in a .dex file cannot exceed 64K.
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html'''; Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
final File manifest = globals.fs.currentDirectory final File manifest = fileSystem.currentDirectory
.childDirectory('android') .childDirectory('android')
.childDirectory('app') .childDirectory('app')
.childDirectory('src') .childDirectory('src')
...@@ -443,7 +489,7 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -443,7 +489,7 @@ Execution failed for task ':app:mergeDexDebug'.
''', flush: true); ''', flush: true);
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: true), equals(GradleBuildStatus.exit)); expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(fileSystem.currentDirectory), multidexEnabled: true, line: '', usesAndroidX: true), equals(GradleBuildStatus.exit));
expect(testLogger.statusText, expect(testLogger.statusText,
contains( contains(
...@@ -461,8 +507,8 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -461,8 +507,8 @@ Execution failed for task ':app:mergeDexDebug'.
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
AnsiTerminal: () => _TestPromptTerminal('n'), AnsiTerminal: () => _TestPromptTerminal('n'),
}); });
...@@ -491,7 +537,7 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -491,7 +537,7 @@ Execution failed for task ':app:mergeDexDebug'.
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html'''; Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: false), equals(GradleBuildStatus.exit)); expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(fileSystem.currentDirectory), multidexEnabled: false, line: '', usesAndroidX: true), equals(GradleBuildStatus.exit));
expect(testLogger.statusText, expect(testLogger.statusText,
contains( contains(
...@@ -499,8 +545,8 @@ Execution failed for task ':app:mergeDexDebug'. ...@@ -499,8 +545,8 @@ Execution failed for task ':app:mergeDexDebug'.
) )
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => processManager,
}); });
}); });
...@@ -511,7 +557,12 @@ Permission denied ...@@ -511,7 +557,12 @@ Permission denied
Command: /home/android/gradlew assembleRelease Command: /home/android/gradlew assembleRelease
'''; ''';
expect(formatTestErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue); expect(formatTestErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue);
expect(await permissionDeniedErrorHandler.handler(), equals(GradleBuildStatus.exit)); expect(await permissionDeniedErrorHandler.handler(
usesAndroidX: true,
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
), equals(GradleBuildStatus.exit));
expect( expect(
testLogger.statusText, testLogger.statusText,
...@@ -541,7 +592,12 @@ Command: /home/android/gradlew assembleRelease ...@@ -541,7 +592,12 @@ Command: /home/android/gradlew assembleRelease
}); });
testUsingContext('handler', () async { testUsingContext('handler', () async {
expect(await permissionDeniedErrorHandler.handler(), equals(GradleBuildStatus.exit)); expect(await permissionDeniedErrorHandler.handler(
usesAndroidX: true,
line: '',
multidexEnabled: true,
project: FakeFlutterProject(),
), equals(GradleBuildStatus.exit));
expect( expect(
testLogger.statusText, testLogger.statusText,
...@@ -574,7 +630,9 @@ Command: /home/android/gradlew assembleRelease ...@@ -574,7 +630,9 @@ Command: /home/android/gradlew assembleRelease
testUsingContext('handler', () async { testUsingContext('handler', () async {
await licenseNotAcceptedHandler.handler( await licenseNotAcceptedHandler.handler(
line: 'You have not accepted the license agreements of the following SDK components: [foo, bar]', line: 'You have not accepted the license agreements of the following SDK components: [foo, bar]',
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
multidexEnabled: true,
); );
expect( expect(
...@@ -594,12 +652,6 @@ Command: /home/android/gradlew assembleRelease ...@@ -594,12 +652,6 @@ Command: /home/android/gradlew assembleRelease
}); });
group('flavor undefined', () { group('flavor undefined', () {
FakeProcessManager fakeProcessManager;
setUp(() {
fakeProcessManager = FakeProcessManager.empty();
});
testWithoutContext('pattern', () { testWithoutContext('pattern', () {
expect( expect(
flavorUndefinedHandler.test( flavorUndefinedHandler.test(
...@@ -628,7 +680,7 @@ Command: /home/android/gradlew assembleRelease ...@@ -628,7 +680,7 @@ Command: /home/android/gradlew assembleRelease
}); });
testUsingContext('handler - with flavor', () async { testUsingContext('handler - with flavor', () async {
fakeProcessManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>[ command: <String>[
'gradlew', 'gradlew',
'app:tasks' , 'app:tasks' ,
...@@ -649,7 +701,10 @@ assembleFooTest ...@@ -649,7 +701,10 @@ assembleFooTest
)); ));
await flavorUndefinedHandler.handler( await flavorUndefinedHandler.handler(
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
line: '',
multidexEnabled: true,
); );
expect( expect(
...@@ -671,16 +726,16 @@ assembleFooTest ...@@ -671,16 +726,16 @@ assembleFooTest
'└─────────────────────────────────────────────────────────────────────────────────────────────────┘\n' '└─────────────────────────────────────────────────────────────────────────────────────────────────┘\n'
) )
); );
expect(fakeProcessManager, hasNoRemainingExpectations); expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
ProcessManager: () => fakeProcessManager, FileSystem: () => fileSystem,
FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => processManager,
}); });
testUsingContext('handler - without flavor', () async { testUsingContext('handler - without flavor', () async {
fakeProcessManager.addCommand(const FakeCommand( processManager.addCommand(const FakeCommand(
command: <String>[ command: <String>[
'gradlew', 'gradlew',
'app:tasks' , 'app:tasks' ,
...@@ -695,7 +750,10 @@ assembleProfile ...@@ -695,7 +750,10 @@ assembleProfile
)); ));
await flavorUndefinedHandler.handler( await flavorUndefinedHandler.handler(
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
line: '',
multidexEnabled: true,
); );
expect( expect(
...@@ -710,12 +768,12 @@ assembleProfile ...@@ -710,12 +768,12 @@ assembleProfile
'└───────────────────────────────────────────────────────────────────────────────────────────────┘\n' '└───────────────────────────────────────────────────────────────────────────────────────────────┘\n'
) )
); );
expect(fakeProcessManager, hasNoRemainingExpectations); expect(processManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
ProcessManager: () => fakeProcessManager, FileSystem: () => fileSystem,
FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => processManager,
}); });
}); });
...@@ -732,7 +790,9 @@ assembleProfile ...@@ -732,7 +790,9 @@ assembleProfile
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
await minSdkVersionHandler.handler( await minSdkVersionHandler.handler(
line: stdoutLine, line: stdoutLine,
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
multidexEnabled: true,
); );
expect( expect(
...@@ -759,8 +819,8 @@ assembleProfile ...@@ -759,8 +819,8 @@ assembleProfile
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -777,7 +837,10 @@ assembleProfile ...@@ -777,7 +837,10 @@ assembleProfile
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
await transformInputIssueHandler.handler( await transformInputIssueHandler.handler(
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
line: '',
multidexEnabled: true,
); );
expect( expect(
...@@ -798,8 +861,8 @@ assembleProfile ...@@ -798,8 +861,8 @@ assembleProfile
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -819,7 +882,10 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'. ...@@ -819,7 +882,10 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'.
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
await lockFileDepMissingHandler.handler( await lockFileDepMissingHandler.handler(
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
line: '',
multidexEnabled: true,
); );
expect( expect(
...@@ -836,8 +902,8 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'. ...@@ -836,8 +902,8 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'.
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -855,7 +921,10 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'. ...@@ -855,7 +921,10 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'.
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
await incompatibleKotlinVersionHandler.handler( await incompatibleKotlinVersionHandler.handler(
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
line: '',
multidexEnabled: true,
); );
expect( expect(
...@@ -873,8 +942,8 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'. ...@@ -873,8 +942,8 @@ Execution failed for task ':app:generateDebugFeatureTransitiveDeps'.
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -895,7 +964,9 @@ A problem occurred evaluating project ':app'. ...@@ -895,7 +964,9 @@ A problem occurred evaluating project ':app'.
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
await outdatedGradleHandler.handler( await outdatedGradleHandler.handler(
line: errorMessage, line: errorMessage,
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
multidexEnabled: true,
); );
expect( expect(
...@@ -918,8 +989,8 @@ A problem occurred evaluating project ':app'. ...@@ -918,8 +989,8 @@ A problem occurred evaluating project ':app'.
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -952,7 +1023,9 @@ Execution failed for task ':app:checkDebugAarMetadata'. ...@@ -952,7 +1023,9 @@ Execution failed for task ':app:checkDebugAarMetadata'.
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
await minCompileSdkVersionHandler.handler( await minCompileSdkVersionHandler.handler(
line: errorMessage, line: errorMessage,
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
usesAndroidX: true,
multidexEnabled: true,
); );
expect( expect(
...@@ -971,8 +1044,8 @@ Execution failed for task ':app:checkDebugAarMetadata'. ...@@ -971,8 +1044,8 @@ Execution failed for task ':app:checkDebugAarMetadata'.
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -994,7 +1067,12 @@ A problem occurred evaluating project ':flutter'. ...@@ -994,7 +1067,12 @@ A problem occurred evaluating project ':flutter'.
}); });
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
await jvm11RequiredHandler.handler(); await jvm11RequiredHandler.handler(
project: FakeFlutterProject(),
usesAndroidX: true,
line: '',
multidexEnabled: true,
);
expect( expect(
testLogger.statusText, testLogger.statusText,
...@@ -1013,8 +1091,8 @@ A problem occurred evaluating project ':flutter'. ...@@ -1013,8 +1091,8 @@ A problem occurred evaluating project ':flutter'.
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -1069,7 +1147,12 @@ at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:108)''' ...@@ -1069,7 +1147,12 @@ at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:108)'''
}); });
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
final GradleBuildStatus status = await sslExceptionHandler.handler(); final GradleBuildStatus status = await sslExceptionHandler.handler(
project: FakeFlutterProject(),
usesAndroidX: true,
line: '',
multidexEnabled: true,
);
expect(status, GradleBuildStatus.retry); expect(status, GradleBuildStatus.retry);
expect(testLogger.errorText, expect(testLogger.errorText,
...@@ -1080,8 +1163,8 @@ at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:108)''' ...@@ -1080,8 +1163,8 @@ at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:108)'''
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
Platform: () => fakePlatform('android'), Platform: () => fakePlatform('android'),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
}); });
}); });
...@@ -1108,12 +1191,17 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''' ...@@ -1108,12 +1191,17 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''
}); });
testUsingContext('suggestion', () async { testUsingContext('suggestion', () async {
globals.fs.file('foo/.gradle/fizz.zip').createSync(recursive: true); fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
final GradleBuildStatus result = await zipExceptionHandler.handler(); final GradleBuildStatus result = await zipExceptionHandler.handler(
project: FakeFlutterProject(),
usesAndroidX: true,
line: '',
multidexEnabled: true,
);
expect(result, equals(GradleBuildStatus.retry)); expect(result, equals(GradleBuildStatus.retry));
expect(globals.fs.file('foo/.gradle/fizz.zip'), exists); expect(fileSystem.file('foo/.gradle/fizz.zip'), exists);
expect( expect(
testLogger.errorText, testLogger.errorText,
contains( contains(
...@@ -1123,18 +1211,23 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''' ...@@ -1123,18 +1211,23 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''
expect(testLogger.statusText, ''); expect(testLogger.statusText, '');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}), Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
BotDetector: () => const FakeBotDetector(false), BotDetector: () => const FakeBotDetector(false),
}); });
testUsingContext('suggestion if running as bot', () async { testUsingContext('suggestion if running as bot', () async {
globals.fs.file('foo/.gradle/fizz.zip').createSync(recursive: true); fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
final GradleBuildStatus result = await zipExceptionHandler.handler(); final GradleBuildStatus result = await zipExceptionHandler.handler(
project: FakeFlutterProject(),
usesAndroidX: true,
line: '',
multidexEnabled: true,
);
expect(result, equals(GradleBuildStatus.retry)); expect(result, equals(GradleBuildStatus.retry));
expect(globals.fs.file('foo/.gradle/fizz.zip'), isNot(exists)); expect(fileSystem.file('foo/.gradle/fizz.zip'), isNot(exists));
expect( expect(
testLogger.errorText, testLogger.errorText,
...@@ -1148,18 +1241,23 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''' ...@@ -1148,18 +1241,23 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}), Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
BotDetector: () => const FakeBotDetector(true), BotDetector: () => const FakeBotDetector(true),
}); });
testUsingContext('suggestion if stdin has terminal and user entered y', () async { testUsingContext('suggestion if stdin has terminal and user entered y', () async {
globals.fs.file('foo/.gradle/fizz.zip').createSync(recursive: true); fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
final GradleBuildStatus result = await zipExceptionHandler.handler(); final GradleBuildStatus result = await zipExceptionHandler.handler(
line: '',
usesAndroidX: true,
multidexEnabled: true,
project: FakeFlutterProject(),
);
expect(result, equals(GradleBuildStatus.retry)); expect(result, equals(GradleBuildStatus.retry));
expect(globals.fs.file('foo/.gradle/fizz.zip'), isNot(exists)); expect(fileSystem.file('foo/.gradle/fizz.zip'), isNot(exists));
expect( expect(
testLogger.errorText, testLogger.errorText,
contains( contains(
...@@ -1172,19 +1270,24 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''' ...@@ -1172,19 +1270,24 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}), Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
AnsiTerminal: () => _TestPromptTerminal('y'), AnsiTerminal: () => _TestPromptTerminal('y'),
BotDetector: () => const FakeBotDetector(false), BotDetector: () => const FakeBotDetector(false),
}); });
testUsingContext('suggestion if stdin has terminal and user entered n', () async { testUsingContext('suggestion if stdin has terminal and user entered n', () async {
globals.fs.file('foo/.gradle/fizz.zip').createSync(recursive: true); fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
final GradleBuildStatus result = await zipExceptionHandler.handler(); final GradleBuildStatus result = await zipExceptionHandler.handler(
line: '',
usesAndroidX: true,
multidexEnabled: true,
project: FakeFlutterProject(),
);
expect(result, equals(GradleBuildStatus.retry)); expect(result, equals(GradleBuildStatus.retry));
expect(globals.fs.file('foo/.gradle/fizz.zip'), exists); expect(fileSystem.file('foo/.gradle/fizz.zip'), exists);
expect( expect(
testLogger.errorText, testLogger.errorText,
contains( contains(
...@@ -1194,8 +1297,8 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''' ...@@ -1194,8 +1297,8 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''
expect(testLogger.statusText, ''); expect(testLogger.statusText, '');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}), Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.empty(), ProcessManager: () => processManager,
AnsiTerminal: () => _TestPromptTerminal('n'), AnsiTerminal: () => _TestPromptTerminal('n'),
BotDetector: () => const FakeBotDetector(false), BotDetector: () => const FakeBotDetector(false),
}); });
...@@ -1217,7 +1320,7 @@ Platform fakePlatform(String name) { ...@@ -1217,7 +1320,7 @@ Platform fakePlatform(String name) {
); );
} }
class FakeGradleUtils extends GradleUtils { class FakeGradleUtils extends Fake implements GradleUtils {
@override @override
String getExecutable(FlutterProject project) { String getExecutable(FlutterProject project) {
return 'gradlew'; return 'gradlew';
...@@ -1226,21 +1329,24 @@ class FakeGradleUtils extends GradleUtils { ...@@ -1226,21 +1329,24 @@ class FakeGradleUtils extends GradleUtils {
/// Simple terminal that returns the specified string when /// Simple terminal that returns the specified string when
/// promptForCharInput is called. /// promptForCharInput is called.
class _TestPromptTerminal extends AnsiTerminal { class _TestPromptTerminal extends Fake implements AnsiTerminal {
_TestPromptTerminal(this.promptResult); _TestPromptTerminal(this.promptResult);
final String promptResult; final String promptResult;
@override @override
Future<String> promptForCharInput(List<String> acceptedCharacters, { bool get stdinHasTerminal => true;
Logger logger,
String prompt, @override
int defaultChoiceIndex, Future<String> promptForCharInput(
List<String> acceptedCharacters, {
required Logger logger,
String? prompt,
int? defaultChoiceIndex,
bool displayAcceptedCharacters = true, bool displayAcceptedCharacters = true,
}) { }) {
return Future<String>.value(promptResult); return Future<String>.value(promptResult);
} }
@override
bool get stdinHasTerminal => true;
} }
class FakeFlutterProject extends Fake implements FlutterProject {}
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
...@@ -32,13 +30,6 @@ void main() { ...@@ -32,13 +30,6 @@ void main() {
group('Project', () { group('Project', () {
group('construction', () { group('construction', () {
_testInMemory('fails on null directory', () async {
expect(
() => FlutterProject.fromDirectory(null),
throwsAssertionError,
);
});
testWithoutContext('invalid utf8 throws a tool exit', () { testWithoutContext('invalid utf8 throws a tool exit', () {
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final FlutterProjectFactory projectFactory = FlutterProjectFactory( final FlutterProjectFactory projectFactory = FlutterProjectFactory(
...@@ -226,7 +217,8 @@ void main() { ...@@ -226,7 +217,8 @@ void main() {
// v1 embedding, as opposed to having <meta-data // v1 embedding, as opposed to having <meta-data
// android:name="flutterEmbedding" android:value="2" />. // android:name="flutterEmbedding" android:value="2" />.
project.checkForDeprecation(deprecationBehavior: DeprecationBehavior.none); // Default is "DeprecationBehavior.none"
project.checkForDeprecation();
expect(testLogger.statusText, isEmpty); expect(testLogger.statusText, isEmpty);
}); });
_testInMemory('Android project not on v2 embedding ignore continues', () async { _testInMemory('Android project not on v2 embedding ignore continues', () async {
...@@ -350,8 +342,8 @@ void main() { ...@@ -350,8 +342,8 @@ void main() {
final FlutterManifest manifest = FlutterManifest.createFromString(''' final FlutterManifest manifest = FlutterManifest.createFromString('''
name: test name: test
version: 1.0.0+3 version: 1.0.0+3
''', logger: BufferLogger.test()); ''', logger: BufferLogger.test())!;
final FlutterProject project = FlutterProject(fileSystem.systemTempDirectory,manifest,manifest); final FlutterProject project = FlutterProject(fileSystem.systemTempDirectory, manifest, manifest);
final Map<String, dynamic> versionInfo = jsonDecode(project.getVersionInfo()) as Map<String, dynamic>; final Map<String, dynamic> versionInfo = jsonDecode(project.getVersionInfo()) as Map<String, dynamic>;
expect(versionInfo['app_name'],'test'); expect(versionInfo['app_name'],'test');
expect(versionInfo['version'],'1.0.0'); expect(versionInfo['version'],'1.0.0');
...@@ -397,9 +389,9 @@ void main() { ...@@ -397,9 +389,9 @@ void main() {
}); });
group('language', () { group('language', () {
XcodeProjectInterpreter xcodeProjectInterpreter; late XcodeProjectInterpreter xcodeProjectInterpreter;
MemoryFileSystem fs; late MemoryFileSystem fs;
FlutterProjectFactory flutterProjectFactory; late FlutterProjectFactory flutterProjectFactory;
setUp(() { setUp(() {
fs = MemoryFileSystem.test(); fs = MemoryFileSystem.test();
xcodeProjectInterpreter = XcodeProjectInterpreter.test(processManager: FakeProcessManager.any()); xcodeProjectInterpreter = XcodeProjectInterpreter.test(processManager: FakeProcessManager.any());
...@@ -434,14 +426,14 @@ apply plugin: 'kotlin-android' ...@@ -434,14 +426,14 @@ apply plugin: 'kotlin-android'
}); });
group('product bundle identifier', () { group('product bundle identifier', () {
MemoryFileSystem fs; late MemoryFileSystem fs;
FakePlistParser testPlistUtils; late FakePlistParser testPlistUtils;
MockXcodeProjectInterpreter mockXcodeProjectInterpreter; late FakeXcodeProjectInterpreter xcodeProjectInterpreter;
FlutterProjectFactory flutterProjectFactory; late FlutterProjectFactory flutterProjectFactory;
setUp(() { setUp(() {
fs = MemoryFileSystem.test(); fs = MemoryFileSystem.test();
testPlistUtils = FakePlistParser(); testPlistUtils = FakePlistParser();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); xcodeProjectInterpreter = FakeXcodeProjectInterpreter();
flutterProjectFactory = FlutterProjectFactory( flutterProjectFactory = FlutterProjectFactory(
fileSystem: fs, fileSystem: fs,
logger: logger, logger: logger,
...@@ -453,7 +445,7 @@ apply plugin: 'kotlin-android' ...@@ -453,7 +445,7 @@ apply plugin: 'kotlin-android'
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
PlistParser: () => testPlistUtils, PlistParser: () => testPlistUtils,
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, XcodeProjectInterpreter: () => xcodeProjectInterpreter,
FlutterProjectFactory: () => flutterProjectFactory, FlutterProjectFactory: () => flutterProjectFactory,
}); });
} }
...@@ -466,16 +458,18 @@ apply plugin: 'kotlin-android' ...@@ -466,16 +458,18 @@ apply plugin: 'kotlin-android'
testWithMocks('from build settings, if no plist', () async { testWithMocks('from build settings, if no plist', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.ios.xcodeProject.createSync(); project.ios.xcodeProject.createSync();
mockXcodeProjectInterpreter.buildSettings = <String, String>{ xcodeProjectInterpreter.buildSettings = <String, String>{
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
}; };
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger); xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject'); expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
}); });
testWithMocks('from project file, if no plist or build settings', () async { testWithMocks('from project file, if no plist or build settings', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
addIosProjectFile(project.directory, projectFileContent: () { addIosProjectFile(project.directory, projectFileContent: () {
return projectFileWithBundleId('io.flutter.someProject'); return projectFileWithBundleId('io.flutter.someProject');
}); });
...@@ -492,10 +486,10 @@ apply plugin: 'kotlin-android' ...@@ -492,10 +486,10 @@ apply plugin: 'kotlin-android'
testWithMocks('from build settings and plist, if default variable', () async { testWithMocks('from build settings and plist, if default variable', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.ios.xcodeProject.createSync(); project.ios.xcodeProject.createSync();
mockXcodeProjectInterpreter.buildSettings = <String, String>{ xcodeProjectInterpreter.buildSettings = <String, String>{
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
}; };
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger); xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
testPlistUtils.setProperty('CFBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER)'); testPlistUtils.setProperty('CFBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER)');
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject'); expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
...@@ -505,11 +499,11 @@ apply plugin: 'kotlin-android' ...@@ -505,11 +499,11 @@ apply plugin: 'kotlin-android'
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.ios.xcodeProject.createSync(); project.ios.xcodeProject.createSync();
project.ios.defaultHostInfoPlist.createSync(recursive: true); project.ios.defaultHostInfoPlist.createSync(recursive: true);
mockXcodeProjectInterpreter.buildSettings = <String, String>{ xcodeProjectInterpreter.buildSettings = <String, String>{
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
'SUFFIX': 'suffix', 'SUFFIX': 'suffix',
}; };
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger); xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
testPlistUtils.setProperty('CFBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER).$(SUFFIX)'); testPlistUtils.setProperty('CFBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER).$(SUFFIX)');
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject.suffix'); expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject.suffix');
...@@ -518,7 +512,7 @@ apply plugin: 'kotlin-android' ...@@ -518,7 +512,7 @@ apply plugin: 'kotlin-android'
testWithMocks('fails with no flavor and defined schemes', () async { testWithMocks('fails with no flavor and defined schemes', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.ios.xcodeProject.createSync(); project.ios.xcodeProject.createSync();
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['free', 'paid'], logger); xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['free', 'paid'], logger);
await expectToolExitLater( await expectToolExitLater(
project.ios.productBundleIdentifier(null), project.ios.productBundleIdentifier(null),
...@@ -529,10 +523,10 @@ apply plugin: 'kotlin-android' ...@@ -529,10 +523,10 @@ apply plugin: 'kotlin-android'
testWithMocks('handles case insensitive flavor', () async { testWithMocks('handles case insensitive flavor', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.ios.xcodeProject.createSync(); project.ios.xcodeProject.createSync();
mockXcodeProjectInterpreter.buildSettings = <String, String>{ xcodeProjectInterpreter.buildSettings = <String, String>{
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
}; };
mockXcodeProjectInterpreter.xcodeProjectInfo =XcodeProjectInfo(<String>[], <String>[], <String>['Free'], logger); xcodeProjectInterpreter.xcodeProjectInfo =XcodeProjectInfo(<String>[], <String>[], <String>['Free'], logger);
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false);
expect(await project.ios.productBundleIdentifier(buildInfo), 'io.flutter.someProject'); expect(await project.ios.productBundleIdentifier(buildInfo), 'io.flutter.someProject');
...@@ -541,7 +535,7 @@ apply plugin: 'kotlin-android' ...@@ -541,7 +535,7 @@ apply plugin: 'kotlin-android'
testWithMocks('fails with flavor and default schemes', () async { testWithMocks('fails with flavor and default schemes', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.ios.xcodeProject.createSync(); project.ios.xcodeProject.createSync();
mockXcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger); xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false); const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false);
await expectToolExitLater( await expectToolExitLater(
...@@ -552,6 +546,7 @@ apply plugin: 'kotlin-android' ...@@ -552,6 +546,7 @@ apply plugin: 'kotlin-android'
testWithMocks('empty surrounded by quotes', () async { testWithMocks('empty surrounded by quotes', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
addIosProjectFile(project.directory, projectFileContent: () { addIosProjectFile(project.directory, projectFileContent: () {
return projectFileWithBundleId('', qualifier: '"'); return projectFileWithBundleId('', qualifier: '"');
}); });
...@@ -560,6 +555,7 @@ apply plugin: 'kotlin-android' ...@@ -560,6 +555,7 @@ apply plugin: 'kotlin-android'
testWithMocks('surrounded by double quotes', () async { testWithMocks('surrounded by double quotes', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
addIosProjectFile(project.directory, projectFileContent: () { addIosProjectFile(project.directory, projectFileContent: () {
return projectFileWithBundleId('io.flutter.someProject', qualifier: '"'); return projectFileWithBundleId('io.flutter.someProject', qualifier: '"');
}); });
...@@ -568,6 +564,7 @@ apply plugin: 'kotlin-android' ...@@ -568,6 +564,7 @@ apply plugin: 'kotlin-android'
testWithMocks('surrounded by single quotes', () async { testWithMocks('surrounded by single quotes', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
xcodeProjectInterpreter.xcodeProjectInfo = XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger);
addIosProjectFile(project.directory, projectFileContent: () { addIosProjectFile(project.directory, projectFileContent: () {
return projectFileWithBundleId('io.flutter.someProject', qualifier: "'"); return projectFileWithBundleId('io.flutter.someProject', qualifier: "'");
}); });
...@@ -576,11 +573,11 @@ apply plugin: 'kotlin-android' ...@@ -576,11 +573,11 @@ apply plugin: 'kotlin-android'
}); });
group('application bundle name', () { group('application bundle name', () {
MemoryFileSystem fs; late MemoryFileSystem fs;
MockXcodeProjectInterpreter mockXcodeProjectInterpreter; late FakeXcodeProjectInterpreter mockXcodeProjectInterpreter;
setUp(() { setUp(() {
fs = MemoryFileSystem.test(); fs = MemoryFileSystem.test();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); mockXcodeProjectInterpreter = FakeXcodeProjectInterpreter();
}); });
testUsingContext('app product name defaults to Runner.app', () async { testUsingContext('app product name defaults to Runner.app', () async {
...@@ -681,14 +678,14 @@ apply plugin: 'kotlin-android' ...@@ -681,14 +678,14 @@ apply plugin: 'kotlin-android'
}); });
}); });
group('watch companion', () { group('watch companion', () {
MemoryFileSystem fs; late MemoryFileSystem fs;
FakePlistParser testPlistParser; late FakePlistParser testPlistParser;
MockXcodeProjectInterpreter mockXcodeProjectInterpreter; late FakeXcodeProjectInterpreter mockXcodeProjectInterpreter;
FlutterProjectFactory flutterProjectFactory; late FlutterProjectFactory flutterProjectFactory;
setUp(() { setUp(() {
fs = MemoryFileSystem.test(); fs = MemoryFileSystem.test();
testPlistParser = FakePlistParser(); testPlistParser = FakePlistParser();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter(); mockXcodeProjectInterpreter = FakeXcodeProjectInterpreter();
flutterProjectFactory = FlutterProjectFactory( flutterProjectFactory = FlutterProjectFactory(
fileSystem: fs, fileSystem: fs,
logger: logger, logger: logger,
...@@ -697,7 +694,7 @@ apply plugin: 'kotlin-android' ...@@ -697,7 +694,7 @@ apply plugin: 'kotlin-android'
testUsingContext('cannot find bundle identifier', () async { testUsingContext('cannot find bundle identifier', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse); expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], BuildInfo.debug, '123'), isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
...@@ -716,7 +713,7 @@ apply plugin: 'kotlin-android' ...@@ -716,7 +713,7 @@ apply plugin: 'kotlin-android'
testUsingContext('no Info.plist in target', () async { testUsingContext('no Info.plist in target', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse); expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], BuildInfo.debug, '123'), isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
...@@ -729,7 +726,7 @@ apply plugin: 'kotlin-android' ...@@ -729,7 +726,7 @@ apply plugin: 'kotlin-android'
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true); project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse); expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], BuildInfo.debug, '123'), isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
...@@ -743,7 +740,7 @@ apply plugin: 'kotlin-android' ...@@ -743,7 +740,7 @@ apply plugin: 'kotlin-android'
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true); project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
testPlistParser.setProperty('WKCompanionAppBundleIdentifier', 'io.flutter.someOTHERproject'); testPlistParser.setProperty('WKCompanionAppBundleIdentifier', 'io.flutter.someOTHERproject');
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isFalse); expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], BuildInfo.debug, '123'), isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
...@@ -758,7 +755,7 @@ apply plugin: 'kotlin-android' ...@@ -758,7 +755,7 @@ apply plugin: 'kotlin-android'
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true); project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
testPlistParser.setProperty('WKCompanionAppBundleIdentifier', 'io.flutter.someProject'); testPlistParser.setProperty('WKCompanionAppBundleIdentifier', 'io.flutter.someProject');
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isTrue); expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], BuildInfo.debug, '123'), isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
...@@ -776,7 +773,7 @@ apply plugin: 'kotlin-android' ...@@ -776,7 +773,7 @@ apply plugin: 'kotlin-android'
project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true); project.ios.hostAppRoot.childDirectory('WatchTarget').childFile('Info.plist').createSync(recursive: true);
testPlistParser.setProperty('WKCompanionAppBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER)'); testPlistParser.setProperty('WKCompanionAppBundleIdentifier', r'$(PRODUCT_BUNDLE_IDENTIFIER)');
expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], null, '123'), isTrue); expect(await project.ios.containsWatchCompanion(<String>['WatchTarget'], BuildInfo.debug, '123'), isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
...@@ -789,7 +786,7 @@ apply plugin: 'kotlin-android' ...@@ -789,7 +786,7 @@ apply plugin: 'kotlin-android'
} }
Future<FlutterProject> someProject({ Future<FlutterProject> someProject({
String androidManifestOverride, String? androidManifestOverride,
bool includePubspec = false, bool includePubspec = false,
}) async { }) async {
final Directory directory = globals.fs.directory('some_project'); final Directory directory = globals.fs.directory('some_project');
...@@ -934,7 +931,7 @@ void _testInMemory(String description, Future<void> Function() testMethod) { ...@@ -934,7 +931,7 @@ void _testInMemory(String description, Future<void> Function() testMethod) {
), ),
FlutterProjectFactory: () => FlutterProjectFactory( FlutterProjectFactory: () => FlutterProjectFactory(
fileSystem: testFileSystem, fileSystem: testFileSystem,
logger: globals.logger ?? BufferLogger.test(), logger: globals.logger,
), ),
}, },
); );
...@@ -963,7 +960,7 @@ void expectNotExists(FileSystemEntity entity) { ...@@ -963,7 +960,7 @@ void expectNotExists(FileSystemEntity entity) {
expect(entity.existsSync(), isFalse); expect(entity.existsSync(), isFalse);
} }
void addIosProjectFile(Directory directory, {String Function() projectFileContent}) { void addIosProjectFile(Directory directory, {required String Function() projectFileContent}) {
directory directory
.childDirectory('ios') .childDirectory('ios')
.childDirectory('Runner.xcodeproj') .childDirectory('Runner.xcodeproj')
...@@ -972,7 +969,7 @@ void addIosProjectFile(Directory directory, {String Function() projectFileConten ...@@ -972,7 +969,7 @@ void addIosProjectFile(Directory directory, {String Function() projectFileConten
..writeAsStringSync(projectFileContent()); ..writeAsStringSync(projectFileContent());
} }
void addAndroidGradleFile(Directory directory, { String Function() gradleFileContent }) { void addAndroidGradleFile(Directory directory, { required String Function() gradleFileContent }) {
directory directory
.childDirectory('android') .childDirectory('android')
.childDirectory('app') .childDirectory('app')
...@@ -1016,7 +1013,7 @@ flutter: ...@@ -1016,7 +1013,7 @@ flutter:
something_else: something_else:
'''; ''';
String projectFileWithBundleId(String id, {String qualifier}) { String projectFileWithBundleId(String id, {String? qualifier}) {
return ''' return '''
97C147061CF9000F007C117D /* Debug */ = { 97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
...@@ -1066,20 +1063,20 @@ File androidPluginRegistrant(Directory parent) { ...@@ -1066,20 +1063,20 @@ File androidPluginRegistrant(Directory parent) {
.childFile('GeneratedPluginRegistrant.java'); .childFile('GeneratedPluginRegistrant.java');
} }
class MockXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
Map<String, String> buildSettings = <String, String>{}; Map<String, String> buildSettings = <String, String>{};
XcodeProjectInfo xcodeProjectInfo; late XcodeProjectInfo xcodeProjectInfo;
@override @override
Future<Map<String, String>> getBuildSettings(String projectPath, { Future<Map<String, String>> getBuildSettings(String projectPath, {
XcodeProjectBuildContext buildContext, XcodeProjectBuildContext? buildContext,
Duration timeout = const Duration(minutes: 1), Duration timeout = const Duration(minutes: 1),
}) async { }) async {
return buildSettings; return buildSettings;
} }
@override @override
Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async { Future<XcodeProjectInfo> getInfo(String projectPath, {String? projectFilename}) async {
return xcodeProjectInfo; return xcodeProjectInfo;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment