flutter/packages/flutter_tools/test/general.shard/ios/xcresult_test.dart
Andrew Kolos 295a9a2031
provide command to FakeCommand::onRun (#142206)
Part of work on [#101077](https://github.com/flutter/flutter/pull/141194). This is done as a separate PR to avoid a massive diff.

## Context
1. The `FakeCommand` class accepts a list of patterns that's used to match a command given to its `FakeProcessManager`. Since `FakeCommand` can match a list of patterns, not just specifically strings, it can be used to match commands where the exact value of some arguments can't (easily) known ahead of time. For example, a part of the tool may invoke a command with an argument that is the path of a temporarily file that has a randomly-generated basename.
2. The `FakeCommand` class provides on `onRun` parameter, which is a callback that is run when the `FakeProcessManager` runs a command that matches the `FakeCommand` in question.

## Issue
In the event that a `FakeCommand` is constructed using patterns, the test code can't know the exact values used for arguments in the command. This PR proposes changing the type of `onRun` from `VoidCallback?` to `void Function(List<String>)?`. When run, the value `List<String>` parameter will be the full command that the `FakeCommand` matched.

Example:
```dart
FakeCommand(
  command: <Pattern>[
    artifacts.getArtifactPath(Artifact.engineDartBinary),
    'run',
    'vector_graphics_compiler',
    RegExp(r'--input=/.*\.temp'),
    RegExp(r'--output=/.*\.temp'),
  ],
  onRun: (List<String> command) {
    final outputPath = (() { 
      // code to parse `--output` from `command`
    })();
    testFileSystem.file(outputPath).createSync(recursive: true);
  },
)
```
2024-01-25 07:51:25 +00:00

278 lines
12 KiB
Dart

// 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/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 setUpFakeXCResultGetCommand({
required String stdout,
required String tempResultPath,
required Xcode xcode,
int exitCode = 0,
String stderr = '',
}) {
return FakeCommand(
command: <String>[
...xcode.xcrunCommand(),
'xcresulttool',
'get',
'--path',
tempResultPath,
'--format',
'json',
],
stdout: stdout,
stderr: stderr,
exitCode: exitCode,
onRun: (_) {},
);
}
const FakeCommand kWhichSysctlCommand = FakeCommand(
command: <String>[
'which',
'sysctl',
],
);
const FakeCommand kx64CheckCommand = FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
);
XCResultGenerator setupGenerator({
required String resultJson,
int exitCode = 0,
String stderr = '',
}) {
final FakeProcessManager fakeProcessManager =
FakeProcessManager.list(<FakeCommand>[
kWhichSysctlCommand,
kx64CheckCommand,
]);
final Xcode xcode = Xcode.test(
processManager: fakeProcessManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(
processManager: fakeProcessManager,
version: null,
),
);
fakeProcessManager.addCommands(
<FakeCommand>[
setUpFakeXCResultGetCommand(
stdout: resultJson,
tempResultPath: _tempResultPath,
xcode: xcode,
exitCode: exitCode,
stderr: stderr,
),
],
);
final ProcessUtils processUtils = ProcessUtils(
processManager: fakeProcessManager,
logger: BufferLogger.test(),
);
return XCResultGenerator(
resultPath: _tempResultPath,
xcode: xcode,
processUtils: processUtils,
);
}
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 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.',
() 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 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, '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(
'error: `xcresulttool get` process fail should return an `XCResult` with stderr as `parsingErrorMessage`.',
() async {
const String 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 String _tempResultPath = 'temp';