Unverified Commit e4dadf78 authored by Chris Yang's avatar Chris Yang Committed by GitHub

[tool] xcresult issue discarder (#95273)

parent 82bf3189
...@@ -35,7 +35,11 @@ class XCResultGenerator { ...@@ -35,7 +35,11 @@ class XCResultGenerator {
/// ///
/// Calls `xcrun xcresulttool get --path <resultPath> --format json`, /// Calls `xcrun xcresulttool get --path <resultPath> --format json`,
/// then stores the useful information the json into an [XCResult] object. /// then stores the useful information the json into an [XCResult] object.
Future<XCResult> generate() async { ///
/// A`issueDiscarders` can be passed to discard any issues that matches the description of any [XCResultIssueDiscarder] in the list.
Future<XCResult> generate(
{List<XCResultIssueDiscarder> issueDiscarders =
const <XCResultIssueDiscarder>[]}) async {
final RunResult result = await processUtils.run( final RunResult result = await processUtils.run(
<String>[ <String>[
...xcode.xcrunCommand(), ...xcode.xcrunCommand(),
...@@ -62,7 +66,7 @@ class XCResultGenerator { ...@@ -62,7 +66,7 @@ class XCResultGenerator {
return XCResult.failed( return XCResult.failed(
errorMessage: 'xcresult parser: Unrecognized top level json format.'); errorMessage: 'xcresult parser: Unrecognized top level json format.');
} }
return XCResult(resultJson: resultJson); return XCResult(resultJson: resultJson, issueDiscarders: issueDiscarders);
} }
} }
...@@ -72,7 +76,7 @@ class XCResultGenerator { ...@@ -72,7 +76,7 @@ class XCResultGenerator {
/// The result contains useful information such as build errors and warnings. /// The result contains useful information such as build errors and warnings.
class XCResult { class XCResult {
/// Parse the `resultJson` and stores useful informations in the returned `XCResult`. /// Parse the `resultJson` and stores useful informations in the returned `XCResult`.
factory XCResult({required Map<String, Object?> resultJson}) { factory XCResult({required Map<String, Object?> resultJson, List<XCResultIssueDiscarder> issueDiscarders = const <XCResultIssueDiscarder>[]}) {
final List<XCResultIssue> issues = <XCResultIssue>[]; final List<XCResultIssue> issues = <XCResultIssue>[];
final Object? actionsMap = resultJson['actions']; final Object? actionsMap = resultJson['actions'];
if (actionsMap == null || actionsMap is! Map<String, Object?>) { if (actionsMap == null || actionsMap is! Map<String, Object?>) {
...@@ -103,32 +107,13 @@ class XCResult { ...@@ -103,32 +107,13 @@ class XCResult {
return XCResult.failed( return XCResult.failed(
errorMessage: 'xcresult parser: Failed to parse the issues map.'); errorMessage: 'xcresult parser: Failed to parse the issues map.');
} }
List<XCResultIssue> _parseIssuesFromIssueSummariesJson({
required XCResultIssueType type,
required Map<String, Object?> issueSummariesJson,
}) {
final List<XCResultIssue> issues = <XCResultIssue>[];
final Object? errorsList = issueSummariesJson['_values'];
if (errorsList is List<Object?>) {
for (final Object? issueJson in errorsList) {
if (issueJson == null || issueJson is! Map<String, Object?>) {
continue;
}
final XCResultIssue resultIssue = XCResultIssue(
type: type,
issueJson: issueJson,
);
issues.add(resultIssue);
}
}
return issues;
}
final Object? errorSummaries = issuesMap['errorSummaries']; final Object? errorSummaries = issuesMap['errorSummaries'];
if (errorSummaries is Map<String, Object?>) { if (errorSummaries is Map<String, Object?>) {
issues.addAll(_parseIssuesFromIssueSummariesJson( issues.addAll(_parseIssuesFromIssueSummariesJson(
type: XCResultIssueType.error, type: XCResultIssueType.error,
issueSummariesJson: errorSummaries, issueSummariesJson: errorSummaries,
issueDiscarder: issueDiscarders,
)); ));
} }
...@@ -137,6 +122,7 @@ class XCResult { ...@@ -137,6 +122,7 @@ class XCResult {
issues.addAll(_parseIssuesFromIssueSummariesJson( issues.addAll(_parseIssuesFromIssueSummariesJson(
type: XCResultIssueType.warning, type: XCResultIssueType.warning,
issueSummariesJson: warningSummaries, issueSummariesJson: warningSummaries,
issueDiscarder: issueDiscarders,
)); ));
} }
return XCResult._(issues: issues); return XCResult._(issues: issues);
...@@ -210,7 +196,8 @@ class XCResultIssue { ...@@ -210,7 +196,8 @@ class XCResultIssue {
if (urlValue is String) { if (urlValue is String) {
location = _convertUrlToLocationString(urlValue); location = _convertUrlToLocationString(urlValue);
if (location == null) { if (location == null) {
warnings.add('(XCResult) The `url` exists but it was failed to be parsed. url: $urlValue'); warnings.add(
'(XCResult) The `url` exists but it was failed to be parsed. url: $urlValue');
} }
} }
} }
...@@ -270,6 +257,39 @@ enum XCResultIssueType { ...@@ -270,6 +257,39 @@ enum XCResultIssueType {
error, error,
} }
/// Discards the [XCResultIssue] that matches any of the matchers.
class XCResultIssueDiscarder {
XCResultIssueDiscarder(
{this.typeMatcher,
this.subTypeMatcher,
this.messageMatcher,
this.locationMatcher})
: assert(typeMatcher != null ||
subTypeMatcher != null ||
messageMatcher != null ||
locationMatcher != null);
/// The type of the discarder.
///
/// A [XCResultIssue] should be discarded if its `type` equals to this.
final XCResultIssueType? typeMatcher;
/// The subType of the discarder.
///
/// A [XCResultIssue] should be discarded if its `subType` matches the RegExp.
final RegExp? subTypeMatcher;
/// The message of the discarder.
///
/// A [XCResultIssue] should be discarded if its `message` matches the RegExp.
final RegExp? messageMatcher;
/// The location of the discarder.
///
/// A [XCResultIssue] should be discarded if its `location` matches the RegExp.
final RegExp? locationMatcher;
}
// A typical location url string looks like file:///foo.swift#CharacterRangeLen=0&EndingColumnNumber=82&EndingLineNumber=7&StartingColumnNumber=82&StartingLineNumber=7. // A typical location url string looks like file:///foo.swift#CharacterRangeLen=0&EndingColumnNumber=82&EndingLineNumber=7&StartingColumnNumber=82&StartingLineNumber=7.
// //
// This function converts it to something like: /foo.swift:<StartingLineNumber>:<StartingColumnNumber>. // This function converts it to something like: /foo.swift:<StartingLineNumber>:<StartingColumnNumber>.
...@@ -295,3 +315,60 @@ String? _convertUrlToLocationString(String url) { ...@@ -295,3 +315,60 @@ String? _convertUrlToLocationString(String url) {
} }
return '${fileLocation.path}$startingLineNumber$startingColumnNumber'; return '${fileLocation.path}$startingLineNumber$startingColumnNumber';
} }
// Determine if an `issue` should be discarded based on the `discarder`.
bool _shouldDiscardIssue(
{required XCResultIssue issue, required XCResultIssueDiscarder discarder}) {
if (issue.type == discarder.typeMatcher) {
return true;
}
if (issue.subType != null &&
discarder.subTypeMatcher != null &&
discarder.subTypeMatcher!.hasMatch(issue.subType!)) {
return true;
}
if (issue.message != null &&
discarder.messageMatcher != null &&
discarder.messageMatcher!.hasMatch(issue.message!)) {
return true;
}
if (issue.location != null &&
discarder.locationMatcher != null &&
discarder.locationMatcher!.hasMatch(issue.location!)) {
return true;
}
return false;
}
List<XCResultIssue> _parseIssuesFromIssueSummariesJson({
required XCResultIssueType type,
required Map<String, Object?> issueSummariesJson,
required List<XCResultIssueDiscarder> issueDiscarder,
}) {
final List<XCResultIssue> issues = <XCResultIssue>[];
final Object? errorsList = issueSummariesJson['_values'];
if (errorsList is List<Object?>) {
for (final Object? issueJson in errorsList) {
if (issueJson == null || issueJson is! Map<String, Object?>) {
continue;
}
final XCResultIssue resultIssue = XCResultIssue(
type: type,
issueJson: issueJson,
);
bool discard = false;
for (final XCResultIssueDiscarder discarder in issueDiscarder) {
if (_shouldDiscardIssue(issue: resultIssue, discarder: discarder)) {
discard = true;
break;
}
}
if (discard) {
continue;
}
issues.add(resultIssue);
}
}
return issues;
}
...@@ -128,6 +128,72 @@ void main() { ...@@ -128,6 +128,72 @@ void main() {
expect(result.parsingErrorMessage, isNull); expect(result.parsingErrorMessage, isNull);
}); });
testWithoutContext(
'correctly parse sample result json and discard all warnings', () async {
final XCResultGenerator generator = _setupGenerator(resultJson: kSampleResultJsonWithIssues);
final XCResultIssueDiscarder 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 XCResultIssueDiscarder 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 XCResultIssueDiscarder 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 XCResultIssueDiscarder 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 XCResultIssueDiscarder discardWarnings = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning);
final XCResultIssueDiscarder 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.', testWithoutContext('correctly parse sample result json when no issues.',
() async { () async {
......
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