blob: 5eed0e106b051a51c7dc2cb13fe73abdecbf3e27 [file] [log] [blame]
// Copyright (c) 2018, 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/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/analysis/experiments_impl.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExperimentsTest);
});
}
@reflectiveTest
class ExperimentsTest {
var knownFeatures = <String, ExperimentalFeature>{};
void assertCurrentSdkLanguageVersion(ExperimentStatus status) {
expect(
getSdkLanguageVersion(status),
ExperimentStatus.currentVersion,
);
}
void assertSdkLanguageVersion(ExperimentStatus status, String expectedStr) {
var actual = getSdkLanguageVersion(status);
expect('${actual.major}.${actual.minor}', expectedStr);
}
ExperimentStatus fromStrings2({
required Version sdkLanguageVersion,
required List<String> flags,
}) {
return overrideKnownFeatures(knownFeatures, () {
return ExperimentStatus.fromStrings2(
sdkLanguageVersion: sdkLanguageVersion,
flags: flags,
);
});
}
List<bool> getFlags(ExperimentStatus status) {
return getExperimentalFlags_forTesting(status);
}
Version getSdkLanguageVersion(ExperimentStatus status) {
return getSdkLanguageVersion_forTesting(status);
}
List<ConflictingFlagLists> getValidateCombinationResult(
List<String> flags1, List<String> flags2) {
return overrideKnownFeatures(
knownFeatures, () => validateFlagCombination(flags1, flags2).toList());
}
List<ValidationResult> getValidationResult(List<String> flags) {
return overrideKnownFeatures(
knownFeatures, () => validateFlags(flags).toList());
}
test_currentVersion() {
// We don't care what the current version is, we just want to make sure that
// it parses without error, and that it takes a simple 'major.minor' form.
var currentVersion = ExperimentStatus.currentVersion;
expect(currentVersion.patch, 0);
expect(currentVersion.preRelease, isEmpty);
expect(currentVersion.build, isEmpty);
}
test_fromStrings2_experimentalReleased_shouldBe_requested() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.2.0'),
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: false,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: Version.parse('1.4.0'),
releaseVersion: null,
);
knownFeatures['c'] = ExperimentalFeature(
index: 2,
enableString: 'c',
isEnabledByDefault: false,
isExpired: false,
documentation: 'c',
experimentalReleaseVersion: Version.parse('1.4.0'),
releaseVersion: null,
);
overrideKnownFeatures(knownFeatures, () {
// Only experiments that are explicitly requested can be enabled.
var status = fromStrings2(
sdkLanguageVersion: Version.parse('1.5.0'),
flags: ['b'],
);
assertSdkLanguageVersion(status, '1.5');
expect(getFlags(status), [true, true, false]);
});
}
test_fromStrings2_flags_conflicting_disable_then_enable() {
// Enable takes precedence because it's last
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['no-a', 'a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [true]);
}
test_fromStrings2_flags_conflicting_enable_then_disable() {
// Disable takes precedence because it's last
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['a', 'no-a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [false]);
}
test_fromStrings2_flags_disable_disabled_feature() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['no-a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [false]);
}
test_fromStrings2_flags_disable_enabled_feature() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['no-a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [false]);
}
test_fromStrings2_flags_empty() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: true,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: [],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [false, true]);
}
test_fromStrings2_flags_enable_disabled_feature() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [true]);
}
test_fromStrings2_flags_enable_enabled_feature() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [true]);
}
test_fromStrings2_flags_illegal_use_of_expired_disable() {
// Expired flags are ignored even if they would fail validation.
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['no-a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [true]);
}
test_fromStrings2_flags_illegal_use_of_expired_enable() {
// Expired flags are ignored even if they would fail validation.
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [false]);
}
test_fromStrings2_flags_unnecessary_use_of_expired_disable() {
// Expired flags are ignored.
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['no-a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [false]);
}
test_fromStrings2_flags_unnecessary_use_of_expired_enable() {
// Expired flags are ignored.
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), [true]);
}
test_fromStrings2_flags_unrecognized() {
// Unrecognized flags are ignored.
var status = fromStrings2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: ['a'],
);
assertCurrentSdkLanguageVersion(status);
expect(getFlags(status), <Object>[]);
}
test_fromStrings2_sdkLanguage_allows_experimental() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.2.0'),
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: false,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['c'] = ExperimentalFeature(
index: 2,
enableString: 'c',
isEnabledByDefault: false,
isExpired: false,
documentation: 'c',
experimentalReleaseVersion: null,
releaseVersion: null,
);
overrideKnownFeatures(knownFeatures, () {
var status = fromStrings2(
sdkLanguageVersion: Version.parse('1.5.0'),
flags: ['b', 'c'],
);
assertSdkLanguageVersion(status, '1.5');
expect(getFlags(status), [true, true, true]);
// Restricting to the SDK version does not change anything.
var status2 =
status.restrictToVersion(Version.parse('1.5.0')) as ExperimentStatus;
assertSdkLanguageVersion(status2, '1.5');
expect(getFlags(status2), [true, true, true]);
// Restricting to the previous version disables the experiments.
var status3 =
status.restrictToVersion(Version.parse('1.4.0')) as ExperimentStatus;
assertSdkLanguageVersion(status3, '1.5');
expect(getFlags(status3), [true, false, false]);
});
}
test_fromStrings2_sdkLanguage_allows_experimentalReleased() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.2.0'),
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: false,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: Version.parse('1.4.0'),
releaseVersion: null,
);
knownFeatures['c'] = ExperimentalFeature(
index: 2,
enableString: 'c',
isEnabledByDefault: false,
isExpired: false,
documentation: 'c',
experimentalReleaseVersion: Version.parse('1.5.0'),
releaseVersion: null,
);
knownFeatures['d'] = ExperimentalFeature(
index: 3,
enableString: 'd',
isEnabledByDefault: false,
isExpired: false,
documentation: 'd',
experimentalReleaseVersion: Version.parse('1.6.0'),
releaseVersion: null,
);
overrideKnownFeatures(knownFeatures, () {
var status = fromStrings2(
sdkLanguageVersion: Version.parse('1.5.0'),
flags: ['b', 'c', 'd'],
);
assertSdkLanguageVersion(status, '1.5');
expect(getFlags(status), [true, true, true, true]);
// Restricting to the SDK version does not change anything.
var status2 =
status.restrictToVersion(Version.parse('1.5.0')) as ExperimentStatus;
assertSdkLanguageVersion(status2, '1.5');
expect(getFlags(status2), [true, true, true, true]);
// Restricting to a version disables some experiments.
var status3 =
status.restrictToVersion(Version.parse('1.4.0')) as ExperimentStatus;
assertSdkLanguageVersion(status3, '1.5');
expect(getFlags(status3), [true, true, false, false]);
});
}
test_fromStrings2_sdkLanguage_restricts_released() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.6.0'),
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: true,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.1.0'),
);
var status = fromStrings2(
sdkLanguageVersion: Version.parse('1.5.0'),
flags: [],
);
assertSdkLanguageVersion(status, '1.5');
expect(getFlags(status), [false, true]);
}
test_validateFlagCombination_disable_then_enable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: false,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['c'] = ExperimentalFeature(
index: 2,
enableString: 'c',
isEnabledByDefault: false,
isExpired: false,
documentation: 'c',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var validationResult =
getValidateCombinationResult(['a', 'no-c'], ['no-b', 'c']);
expect(validationResult, hasLength(1));
var error = validationResult[0];
expect(error.feature, knownFeatures['c']);
expect(error.firstValue, false);
}
test_validateFlagCombination_enable_then_disable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: false,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['c'] = ExperimentalFeature(
index: 2,
enableString: 'c',
isEnabledByDefault: false,
isExpired: false,
documentation: 'c',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var validationResult =
getValidateCombinationResult(['a', 'c'], ['no-b', 'no-c']);
expect(validationResult, hasLength(1));
var error = validationResult[0];
expect(error.feature, knownFeatures['c']);
expect(error.firstValue, true);
}
test_validateFlagCombination_ok() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['b'] = ExperimentalFeature(
index: 1,
enableString: 'b',
isEnabledByDefault: false,
isExpired: false,
documentation: 'b',
experimentalReleaseVersion: null,
releaseVersion: null,
);
knownFeatures['c'] = ExperimentalFeature(
index: 2,
enableString: 'c',
isEnabledByDefault: false,
isExpired: false,
documentation: 'c',
experimentalReleaseVersion: null,
releaseVersion: null,
);
expect(getValidateCombinationResult(['a', 'c'], ['no-b', 'c']), isEmpty);
}
test_validateFlags_conflicting_flags_disable_then_enable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var validationResult = getValidationResult(['no-a', 'a']);
expect(validationResult, hasLength(1));
var error = validationResult[0] as ConflictingFlags;
expect(error.stringIndex, 1);
expect(error.flag, 'a');
expect(error.isError, true);
expect(error.feature, knownFeatures['a']);
expect(error.previousStringIndex, 0);
expect(error.requestedValue, true);
}
test_validateFlags_conflicting_flags_enable_then_disable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var validationResult = getValidationResult(['a', 'no-a']);
expect(validationResult, hasLength(1));
var error = validationResult[0] as ConflictingFlags;
expect(error.stringIndex, 1);
expect(error.flag, 'no-a');
expect(error.isError, true);
expect(error.feature, knownFeatures['a']);
expect(error.previousStringIndex, 0);
expect(error.requestedValue, false);
}
test_validateFlags_ignore_redundant_disable_flags() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
expect(getValidationResult(['no-a', 'no-a']), isEmpty);
}
test_validateFlags_ignore_redundant_enable_flags() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: false,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
expect(getValidationResult(['a', 'a']), isEmpty);
}
test_validateFlags_illegal_use_of_expired_flag_disable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
var validationResult = getValidationResult(['no-a']);
expect(validationResult, hasLength(1));
var error = validationResult[0] as IllegalUseOfExpiredFlag;
expect(error.stringIndex, 0);
expect(error.flag, 'no-a');
expect(error.isError, true);
expect(error.feature, knownFeatures['a']);
}
test_validateFlags_illegal_use_of_expired_flag_enable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var validationResult = getValidationResult(['a']);
expect(validationResult, hasLength(1));
var error = validationResult[0] as IllegalUseOfExpiredFlag;
expect(error.stringIndex, 0);
expect(error.flag, 'a');
expect(error.isError, true);
expect(error.feature, knownFeatures['a']);
}
test_validateFlags_unnecessary_use_of_expired_flag_disable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: false,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: null,
);
var validationResult = getValidationResult(['no-a']);
expect(validationResult, hasLength(1));
var error = validationResult[0] as UnnecessaryUseOfExpiredFlag;
expect(error.stringIndex, 0);
expect(error.flag, 'no-a');
expect(error.isError, false);
expect(error.feature, knownFeatures['a']);
}
test_validateFlags_unnecessary_use_of_expired_flag_enable() {
knownFeatures['a'] = ExperimentalFeature(
index: 0,
enableString: 'a',
isEnabledByDefault: true,
isExpired: true,
documentation: 'a',
experimentalReleaseVersion: null,
releaseVersion: Version.parse('1.0.0'),
);
var validationResult = getValidationResult(['a']);
expect(validationResult, hasLength(1));
var error = validationResult[0] as UnnecessaryUseOfExpiredFlag;
expect(error.stringIndex, 0);
expect(error.flag, 'a');
expect(error.isError, false);
expect(error.feature, knownFeatures['a']);
}
test_validateFlags_unrecognized_flag() {
var validationResult = getValidationResult(['a']);
expect(validationResult, hasLength(1));
var error = validationResult[0] as UnrecognizedFlag;
expect(error.stringIndex, 0);
expect(error.flag, 'a');
expect(error.isError, true);
}
}