blob: 7ce39da7d3a04fdcbe6c4debe07305fc5702330e [file] [log] [blame]
// 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.
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/ios/xcresult.dart';
import 'package:flutter_tools/src/macos/xcode.dart';
import '../../src/common.dart';
import '../../src/fake_process_manager.dart';
import 'xcresult_test_data.dart';
void main() {
// Creates a FakeCommand for the xcresult get call to build the app
// in the given configuration.
FakeCommand setUpFakeXCResultCommand({
required String stdout,
required String tempResultPath,
required Xcode xcode,
required Version? xcodeVersion,
int exitCode = 0,
String stderr = '',
}) {
final bool useNewCommand = xcodeVersion != null && xcodeVersion >= Version(16, 0, 0);
final List<String> command;
if (useNewCommand) {
command = <String>[
...xcode.xcrunCommand(),
'xcresulttool',
'get',
'build-results',
'--path',
tempResultPath,
'--format',
'json',
];
} else {
command = <String>[
...xcode.xcrunCommand(),
'xcresulttool',
'get',
'--path',
tempResultPath,
'--format',
'json',
];
}
return FakeCommand(
command: command,
stdout: stdout,
stderr: stderr,
exitCode: exitCode,
onRun: (_) {},
);
}
const kWhichSysctlCommand = FakeCommand(command: <String>['which', 'sysctl']);
const kx64CheckCommand = FakeCommand(
command: <String>['sysctl', 'hw.optional.arm64'],
exitCode: 1,
);
XCResultGenerator setupGenerator({
required String resultJson,
int exitCode = 0,
String stderr = '',
// Default to an pre-Xcode 16 version of Xcode.
// This ensures that tests which don't explicitly set an Xcode version still cover
// the logic for pre-Xcode 16 platforms.
Version? xcodeVersion = const Version.withText(15, 0, 0, '15.0'),
}) {
final fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
kWhichSysctlCommand,
kx64CheckCommand,
]);
final xcode = Xcode.test(
processManager: fakeProcessManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(
processManager: fakeProcessManager,
version: xcodeVersion,
),
);
fakeProcessManager.addCommands(<FakeCommand>[
setUpFakeXCResultCommand(
stdout: resultJson,
tempResultPath: _tempResultPath,
xcode: xcode,
exitCode: exitCode,
stderr: stderr,
xcodeVersion: xcodeVersion,
),
]);
final processUtils = ProcessUtils(
processManager: fakeProcessManager,
logger: BufferLogger.test(),
);
return XCResultGenerator(resultPath: _tempResultPath, xcode: xcode, processUtils: processUtils);
}
testWithoutContext('correctly parses new format (Xcode 16+) JSON with issues', () async {
final XCResultGenerator generator = setupGenerator(
resultJson: kNewFormatResultJsonWithIssues,
xcodeVersion: Version(16, 0, 0),
);
final XCResult result = await generator.generate();
expect(result.issues.length, 2);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
final XCResultIssue error = result.issues.firstWhere(
(issue) => issue.type == XCResultIssueType.error,
);
expect(error.subType, 'Swift Compiler Error');
expect(error.message, "consecutive statements on a line must be separated by ';'");
expect(error.location, '/Users/m/Projects/test_create/ios/Runner/AppDelegate.swift:11:82');
final XCResultIssue warning = result.issues.firstWhere(
(issue) => issue.type == XCResultIssueType.warning,
);
expect(warning.subType, 'Deprecation');
expect(warning.message, "'openURL' was deprecated in iOS 10.0");
expect(warning.location, '/Users/m/Projects/test_create/ios/Runner/AppDelegate.swift:15:20');
});
testWithoutContext('correctly handles new format (Xcode 16+) with invalid sourceURL', () async {
final XCResultGenerator generator = setupGenerator(
resultJson: kNewFormatResultJsonWithInvalidUrl,
xcodeVersion: Version(16, 0, 0),
);
final XCResult result = await generator.generate();
expect(result.issues.length, 1);
final XCResultIssue error = result.issues.first;
expect(error.location, isNull);
expect(error.warnings.first, contains('failed to be parsed'));
});
testWithoutContext('correctly parse sample result json when there are issues.', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithIssues);
final XCResult result = await generator.generate();
expect(result.issues.length, 2);
expect(result.issues.first.type, XCResultIssueType.error);
expect(result.issues.first.subType, 'Semantic Issue');
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
expect(
result.issues.first.location,
'/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56',
);
expect(result.issues.last.type, XCResultIssueType.warning);
expect(result.issues.last.subType, 'Warning');
expect(
result.issues.last.message,
"The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.",
);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext(
'correctly parse sample result json when there are issues but invalid url.',
() async {
final XCResultGenerator generator = setupGenerator(
resultJson: kSampleResultJsonWithIssuesAndInvalidUrl,
);
final XCResult result = await generator.generate();
expect(result.issues.length, 2);
expect(result.issues.first.type, XCResultIssueType.error);
expect(result.issues.first.subType, 'Semantic Issue');
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
expect(result.issues.first.location, isNull);
expect(
result.issues.first.warnings.first,
'(XCResult) The `url` exists but it was failed to be parsed. url: 3:00',
);
expect(result.issues.last.type, XCResultIssueType.warning);
expect(result.issues.last.subType, 'Warning');
expect(
result.issues.last.message,
"The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.",
);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
},
);
testWithoutContext('correctly parse sample result json and discard all warnings', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithIssues);
final discarder = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
final XCResult result = await generator.generate(
issueDiscarders: <XCResultIssueDiscarder>[discarder],
);
expect(result.issues.length, 1);
expect(result.issues.first.type, XCResultIssueType.error);
expect(result.issues.first.subType, 'Semantic Issue');
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
expect(
result.issues.first.location,
'/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56',
);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext('correctly parse sample result json and discard base on subType', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithIssues);
final discarder = XCResultIssueDiscarder(subTypeMatcher: RegExp(r'^Warning$'));
final XCResult result = await generator.generate(
issueDiscarders: <XCResultIssueDiscarder>[discarder],
);
expect(result.issues.length, 1);
expect(result.issues.first.type, XCResultIssueType.error);
expect(result.issues.first.subType, 'Semantic Issue');
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
expect(
result.issues.first.location,
'/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56',
);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext('correctly parse sample result json and discard base on message', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithIssues);
final discarder = XCResultIssueDiscarder(
messageMatcher: RegExp(
r"^The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.$",
),
);
final XCResult result = await generator.generate(
issueDiscarders: <XCResultIssueDiscarder>[discarder],
);
expect(result.issues.length, 1);
expect(result.issues.first.type, XCResultIssueType.error);
expect(result.issues.first.subType, 'Semantic Issue');
expect(result.issues.first.message, "Use of undeclared identifier 'asdas'");
expect(
result.issues.first.location,
'/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56',
);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext('correctly parse sample result json and discard base on location', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithIssues);
final discarder = XCResultIssueDiscarder(
locationMatcher: RegExp(r'/Users/m/Projects/test_create/ios/Runner/AppDelegate.m'),
);
final XCResult result = await generator.generate(
issueDiscarders: <XCResultIssueDiscarder>[discarder],
);
expect(result.issues.length, 1);
expect(result.issues.first.type, XCResultIssueType.warning);
expect(result.issues.first.subType, 'Warning');
expect(
result.issues.first.message,
"The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.",
);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext('correctly parse sample result json with multiple discarders.', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithIssues);
final discardWarnings = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
final discardSemanticIssues = XCResultIssueDiscarder(
subTypeMatcher: RegExp(r'^Semantic Issue$'),
);
final XCResult result = await generator.generate(
issueDiscarders: <XCResultIssueDiscarder>[discardWarnings, discardSemanticIssues],
);
expect(result.issues, isEmpty);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext('correctly parse sample result json when no issues.', () async {
final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonNoIssues);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext('correctly parse sample result json with action issues.', () async {
final XCResultGenerator generator = setupGenerator(
resultJson: kSampleResultJsonWithActionIssues,
);
final discarder = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
final XCResult result = await generator.generate(
issueDiscarders: <XCResultIssueDiscarder>[discarder],
);
expect(result.issues.length, 1);
expect(result.issues.first.type, XCResultIssueType.error);
expect(result.issues.first.subType, 'Uncategorized');
expect(
result.issues.first.message,
contains('Unable to find a destination matching the provided destination specifier'),
);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext('correctly parse sample result on < Xcode 16.', () async {
final XCResultGenerator generator = setupGenerator(
resultJson: kSampleResultJsonNoIssues,
xcodeVersion: Version(15, 0, 0),
);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, isTrue);
expect(result.parsingErrorMessage, isNull);
});
testWithoutContext(
'error: `xcresulttool get` process fail should return an `XCResult` with stderr as `parsingErrorMessage`.',
() async {
const fakeStderr = 'Fake: fail to parse result json.';
final XCResultGenerator generator = setupGenerator(
resultJson: '',
exitCode: 1,
stderr: fakeStderr,
);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage, fakeStderr);
},
);
testWithoutContext('error: `xcresulttool get` no stdout', () async {
final XCResultGenerator generator = setupGenerator(resultJson: '');
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage, 'xcresult parser: Unrecognized top level json format.');
});
testWithoutContext('error: wrong top level json format.', () async {
final XCResultGenerator generator = setupGenerator(resultJson: '[]');
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage, 'xcresult parser: Unrecognized top level json format.');
});
testWithoutContext('error: fail to parse issue map', () async {
final XCResultGenerator generator = setupGenerator(resultJson: '{}');
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage, 'xcresult parser: Failed to parse the issues map.');
});
testWithoutContext('error: invalid issue map', () async {
final XCResultGenerator generator = setupGenerator(
resultJson: kSampleResultJsonInvalidIssuesMap,
);
final XCResult result = await generator.generate();
expect(result.issues.length, 0);
expect(result.parseSuccess, false);
expect(result.parsingErrorMessage, 'xcresult parser: Failed to parse the issues map.');
});
}
const _tempResultPath = 'temp';