blob: a3f0cc18d5adb1956b5d7a3935b8aac3d8a029b1 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. 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:analyzer/error/error.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/manifest/manifest_validator.dart';
import 'package:analyzer/src/manifest/manifest_values.dart';
import 'package:analyzer/src/manifest/manifest_warning_code.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../generated/test_support.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ManifestValidatorTest);
defineReflectiveTests(ManifestParserTest);
});
}
@reflectiveTest
class ManifestParserTest with ResourceProviderMixin {
static final _manifestUri = Uri.parse('file:///sample/Manifest.xml');
void test_attribute_endsAfterEquals_isError() {
var parser = ManifestParser('<tag a= />', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_attribute_missingValue_isError() {
var parser = ManifestParser('<tag a />', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_attribute_valueMissingQuotes_isError() {
var parser = ManifestParser('<tag a=b />', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_commentTag_isParsed() {
var parser = ManifestParser('''
<!-- comment tag -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.element);
expect(result.element, isNull);
result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_emptyFileDoesNotCrash() {
var parser = ManifestParser('', _manifestUri);
parser.parseXmlTag();
}
void test_endTagWithAttributes_isError() {
var parser = ManifestParser('<tag></tag aaa="bbb">', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_endTagWithWhitespace_isOk() {
var parser = ManifestParser('<tag></tag >', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.element);
}
void test_eofAfterAttributeEqual_isError() {
var parser = ManifestParser('<manifest xml=', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_eofAfterAttributeEqual_whitespace_isError() {
var parser = ManifestParser('<manifest xml= ', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_eofAfterOpeningTag() {
var parser = ManifestParser('<manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.eof);
}
void test_eofAfterOpeningTag_nested_inside() {
var parser = ManifestParser('<manifest><application>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.eof);
}
void test_eofAfterOpeningTag_nested_outside() {
var parser =
ManifestParser('<manifest><application></application>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.eof);
}
void test_eofAfterOpeningTag_whitespace() {
var parser = ManifestParser('<tag> ', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.eof);
}
void test_eofDuringAttributeName_isError() {
var parser = ManifestParser('<tag xml ', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_eofDuringAttributeName_whitespace_isError() {
var parser = ManifestParser('<tag xml', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_eofDuringAttributeValue_isError() {
var parser = ManifestParser('<tag a="b"', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_eofDuringAttributeValue_whitespace_isError() {
var parser = ManifestParser('<tag aaa="bbb" ', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_eofDuringTagName_isError() {
var parser = ManifestParser('<tag', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_eofDuringTagName_whitespace_isError() {
var parser = ManifestParser('<tag ', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
void test_manifestTag_attributeWithEmptyValue_emptyElement_isParsed() {
var parser = ManifestParser('<manifest xmlns:android=""/>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_emptyElement_isParsed() {
var parser = ManifestParser('''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_emptyElement_noAttributes_isParsed() {
var parser = ManifestParser('<manifest/>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_emptyElement_noAttributes_whitespace_isParsed() {
var parser = ManifestParser('<manifest />', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_emptyElement_whitespace_isParsed() {
var parser = ManifestParser('''
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_isParsed() {
var parser = ManifestParser('''
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_uppercase_isParsed() {
var parser = ManifestParser('''
<MANIFEST xmlns:android="http://schemas.android.com/apk/res/android">
</MANIFEST>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_withDoctype_isParsed() {
var parser = ManifestParser('''
<!DOCTYPE greeting SYSTEM "hello.dtd">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.element);
expect(result.element, isNull);
result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_withFeatures_isParsed() {
var parser = ManifestParser('''
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature android:name="android.software.home_screen" />
</manifest>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
var children = result.element!.children;
expect(children, hasLength(2));
expect(children[0].name, equals(USES_FEATURE_TAG));
var touchscreenAttributes = children[0].attributes;
expect(touchscreenAttributes, hasLength(2));
expect(touchscreenAttributes[ANDROID_NAME]!.value,
equals(HARDWARE_FEATURE_TOUCHSCREEN));
expect(touchscreenAttributes[ANDROID_REQUIRED]!.value, equals('false'));
expect(children[1].name, equals(USES_FEATURE_TAG));
var homeScreenAttributes = children[1].attributes;
expect(homeScreenAttributes, hasLength(1));
expect(homeScreenAttributes[ANDROID_NAME]!.value,
equals('android.software.home_screen'));
}
void test_manifestTag_withInnerText_isParsed() {
var parser = ManifestParser('''
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
Text
</manifest>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_withSurroundingText_isParsed() {
var parser = ManifestParser('''
Text
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
Text
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_manifestTag_withXmlTag_isParsed() {
var parser = ManifestParser('''
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
''', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.element);
expect(result.element, isNull);
result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.relevantElement);
expect(result.element!.name, MANIFEST_TAG);
}
void test_outsideTagClosedBeforeInside() {
var parser =
ManifestParser('<manifest><application></manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.eof);
}
void test_relevantTag_attributeIsParsed() {
var parser =
ManifestParser('<manifest aaa="bbb"></manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
expect(result.element!.attributes, hasLength(1));
var attribute = result.element!.attributes['aaa']!;
expect(attribute.name, equals('aaa'));
expect(attribute.value, equals('bbb'));
var sourceSpan = attribute.sourceSpan;
expect(sourceSpan.start.offset, equals(10));
expect(sourceSpan.end.offset, equals(18));
}
void test_relevantTag_attributeIsParsed_containsSingleQuotes() {
var parser =
ManifestParser('<manifest aaa="b\'b\'b"></manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
expect(result.element!.attributes, hasLength(1));
var attribute = result.element!.attributes['aaa']!;
expect(attribute.name, equals('aaa'));
expect(attribute.value, equals("b'b'b"));
var sourceSpan = attribute.sourceSpan;
expect(sourceSpan.start.offset, equals(10));
expect(sourceSpan.end.offset, equals(20));
}
void test_relevantTag_attributeIsParsed_emptyValue() {
var parser = ManifestParser('<manifest aaa=""></manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
expect(result.element!.attributes, hasLength(1));
var attribute = result.element!.attributes['aaa']!;
expect(attribute.name, equals('aaa'));
expect(attribute.value, equals(''));
var sourceSpan = attribute.sourceSpan;
expect(sourceSpan.start.offset, equals(10));
expect(sourceSpan.end.offset, equals(15));
}
void test_relevantTag_attributeIsParsed_singleQuotes() {
var parser =
ManifestParser("<manifest aaa='bbb'></manifest>", _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
expect(result.element!.attributes, hasLength(1));
var attribute = result.element!.attributes['aaa']!;
expect(attribute.name, equals('aaa'));
expect(attribute.value, equals('bbb'));
var sourceSpan = attribute.sourceSpan;
expect(sourceSpan.start.offset, equals(10));
expect(sourceSpan.end.offset, equals(18));
}
void test_relevantTag_attributeIsParsed_uppercase() {
var parser =
ManifestParser('<manifest AAA="bbb"></manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
expect(result.element!.attributes, hasLength(1));
var attribute = result.element!.attributes['aaa']!;
expect(attribute.name, equals('aaa'));
expect(attribute.value, equals('bbb'));
}
void test_relevantTag_attributeWithEmptyValueIsParsed() {
var parser =
ManifestParser('<manifest xmlns:android=""></manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
expect(result.element!.attributes, hasLength(1));
var attribute = result.element!.attributes['xmlns:android']!;
expect(attribute.value, equals(''));
}
void test_relevantTag_emptyElement_nameIsParsed() {
var parser = ManifestParser('<manifest/>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
var sourceSpan = result.element!.sourceSpan!;
expect(sourceSpan.start.offset, equals(0));
expect(sourceSpan.end.offset, equals(10));
}
void test_relevantTag_emptyElement_whitespace_nameIsParsed() {
var parser = ManifestParser('<manifest />', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
var sourceSpan = result.element!.sourceSpan!;
expect(sourceSpan.start.offset, equals(0));
expect(sourceSpan.end.offset, equals(11));
}
void test_relevantTag_withAttributes_emptyElement_nameIsParsed() {
var parser = ManifestParser('<manifest aaa="bbb" />', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
var sourceSpan = result.element!.sourceSpan!;
expect(sourceSpan.start.offset, equals(0));
expect(sourceSpan.end.offset, equals(21));
}
void test_relevantTag_withAttributes_nameIsParsed() {
var parser =
ManifestParser('<manifest aaa="bbb"></manifest>', _manifestUri);
var result = parser.parseXmlTag();
expect(result.element!.name, MANIFEST_TAG);
var sourceSpan = result.element!.sourceSpan!;
expect(sourceSpan.start.offset, equals(0));
expect(sourceSpan.end.offset, equals(30));
}
void test_tagBeginningWithWhitespace_isError() {
var parser = ManifestParser('< tag />', _manifestUri);
var result = parser.parseXmlTag();
expect(result.parseResult, ParseResult.error);
}
}
@reflectiveTest
class ManifestValidatorTest with ResourceProviderMixin {
late final ManifestValidator validator;
/// Assert that when the validator is used on the given [content] the
/// [expectedErrorCodes] are produced.
void assertErrors(String content, List<ErrorCode> expectedErrorCodes) {
List<AnalysisError> errors = validator.validate(content, true);
GatheringErrorListener listener = GatheringErrorListener();
listener.addAll(errors);
listener.assertErrorsWithCodes(expectedErrorCodes);
}
/// Assert that when the validator is used on the given [content] no errors
/// are produced.
void assertNoErrors(String content) {
assertErrors(content, []);
}
void setUp() {
File ManifestFile = getFile('/sample/Manifest.xml');
Source source = ManifestFile.createSource();
validator = ManifestValidator(source);
}
test_cameraPermissions_error() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
</manifest>
''', [ManifestWarningCode.CAMERA_PERMISSIONS_INCOMPATIBLE]);
}
test_cameraPermissions_ok() {
assertNoErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
</manifest>
''');
}
test_featureNotSupported_error() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" />
</manifest>
''', [ManifestWarningCode.UNSUPPORTED_CHROME_OS_HARDWARE]);
}
test_hardwareNotSupported_error() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.home_screen" />
</manifest>
''', [ManifestWarningCode.UNSUPPORTED_CHROME_OS_HARDWARE]);
}
test_no_errors() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<activity android:name="testActivity"
android:resizeableActivity="true"
android:exported="false">
</activity>
</manifest>
''', []);
}
test_noTouchScreen_error() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
''', [ManifestWarningCode.NO_TOUCHSCREEN_FEATURE]);
}
test_resizeableactivity_error() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<application android:label="@string/app_name">
<activity android:name="testActivity"
android:resizeableActivity="false"
android:exported="false">
</activity>
</application>
</manifest>
''', [ManifestWarningCode.NON_RESIZABLE_ACTIVITY]);
}
test_screenOrientation_error() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<application android:label="@string/app_name">
<activity android:name="testActivity"
android:screenOrientation="landscape"
android:exported="false">
</activity>
</application>
</manifest>
''', [ManifestWarningCode.SETTING_ORIENTATION_ON_ACTIVITY]);
}
test_touchScreenNotSupported_error() {
assertErrors('''
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.touchscreen" android:required="true"/>
</manifest>
''', [ManifestWarningCode.UNSUPPORTED_CHROME_OS_FEATURE]);
}
}