@@ -212,10 +212,12 @@ class Configuration {
212212 var mode = enumOption ("mode" , Mode .names, Mode .find);
213213 var runtime = enumOption ("runtime" , Runtime .names, Runtime .find);
214214 var system = enumOption ("system" , System .names, System .find);
215+ var nnbdMode = enumOption ("nnbd" , NnbdMode .names, NnbdMode .find);
215216
216217 // Fill in any missing values using defaults when possible.
217218 architecture ?? = Architecture .x64;
218219 system ?? = System .host;
220+ nnbdMode ?? = NnbdMode .legacy;
219221
220222 // Infer from compiler from runtime or vice versa.
221223 if (compiler == null ) {
@@ -239,10 +241,12 @@ class Configuration {
239241
240242 var configuration = Configuration (
241243 name, architecture, compiler, mode, runtime, system,
244+ nnbdMode: nnbdMode,
242245 babel: stringOption ("babel" ),
243246 builderTag: stringOption ("builder-tag" ),
244247 vmOptions: stringListOption ("vm-options" ),
245248 dart2jsOptions: stringListOption ("dart2js-options" ),
249+ experiments: stringListOption ("experiments" ),
246250 timeout: intOption ("timeout" ),
247251 enableAsserts: boolOption ("enable-asserts" ),
248252 isChecked: boolOption ("checked" ),
@@ -277,6 +281,9 @@ class Configuration {
277281
278282 final System system;
279283
284+ /// Which NNBD mode to run the test files under.
285+ final NnbdMode nnbdMode;
286+
280287 final String babel;
281288
282289 final String builderTag;
@@ -285,6 +292,17 @@ class Configuration {
285292
286293 final List <String > dart2jsOptions;
287294
295+ /// The names of the experiments to enable while running tests.
296+ ///
297+ /// A test may *require* an experiment to always be enabled by containing a
298+ /// comment like:
299+ ///
300+ /// // SharedOptions=--enable-experiment=extension-methods
301+ ///
302+ /// Enabling an experiment here in the configuration allows running the same
303+ /// test both with an experiment on and off.
304+ final List <String > experiments;
305+
288306 final int timeout;
289307
290308 final bool enableAsserts;
@@ -314,10 +332,12 @@ class Configuration {
314332
315333 Configuration (this .name, this .architecture, this .compiler, this .mode,
316334 this .runtime, this .system,
317- {String babel,
335+ {NnbdMode nnbdMode,
336+ String babel,
318337 String builderTag,
319338 List <String > vmOptions,
320339 List <String > dart2jsOptions,
340+ List <String > experiments,
321341 int timeout,
322342 bool enableAsserts,
323343 bool isChecked,
@@ -331,10 +351,12 @@ class Configuration {
331351 bool useHotReload,
332352 bool useHotReloadRollback,
333353 bool useSdk})
334- : babel = babel ?? "" ,
354+ : nnbdMode = nnbdMode ?? NnbdMode .legacy,
355+ babel = babel ?? "" ,
335356 builderTag = builderTag ?? "" ,
336357 vmOptions = vmOptions ?? < String > [],
337358 dart2jsOptions = dart2jsOptions ?? < String > [],
359+ experiments = experiments ?? < String > [],
338360 timeout = timeout ?? - 1 ,
339361 enableAsserts = enableAsserts ?? false ,
340362 isChecked = isChecked ?? false ,
@@ -357,10 +379,12 @@ class Configuration {
357379 mode == other.mode &&
358380 runtime == other.runtime &&
359381 system == other.system &&
382+ nnbdMode == other.nnbdMode &&
360383 babel == other.babel &&
361384 builderTag == other.builderTag &&
362- vmOptions.join (" & " ) == other.vmOptions.join (" & " ) &&
363- dart2jsOptions.join (" & " ) == other.dart2jsOptions.join (" & " ) &&
385+ _listsEqual (vmOptions, other.vmOptions) &&
386+ _listsEqual (dart2jsOptions, other.dart2jsOptions) &&
387+ _listsEqual (experiments, other.experiments) &&
364388 timeout == other.timeout &&
365389 enableAsserts == other.enableAsserts &&
366390 isChecked == other.isChecked &&
@@ -375,6 +399,20 @@ class Configuration {
375399 useHotReloadRollback == other.useHotReloadRollback &&
376400 useSdk == other.useSdk;
377401
402+ /// Whether [a] and [b] contain the same strings, regardless of order.
403+ bool _listsEqual (List <String > a, List <String > b) {
404+ if (a.length != b.length) return false ;
405+
406+ // Using sorted lists instead of sets in case there are duplicate strings
407+ // in the lists. ["a"] should not be considered equal to ["a", "a"].
408+ var aSorted = a.toList ()..sort ();
409+ var bSorted = b.toList ()..sort ();
410+ for (var i = 0 ; i < aSorted.length; i++ ) {
411+ if (aSorted[i] != bSorted[i]) return false ;
412+ }
413+ return true ;
414+ }
415+
378416 bool operator == (Object other) =>
379417 other is Configuration && name == other.name && optionsEqual (other);
380418
@@ -388,10 +426,12 @@ class Configuration {
388426 mode.hashCode ^
389427 runtime.hashCode ^
390428 system.hashCode ^
429+ nnbdMode.hashCode ^
391430 babel.hashCode ^
392431 builderTag.hashCode ^
393432 vmOptions.join (" & " ).hashCode ^
394433 dart2jsOptions.join (" & " ).hashCode ^
434+ experiments.join (" & " ).hashCode ^
395435 timeout.hashCode ^
396436 _toBinary ([
397437 enableAsserts,
@@ -420,12 +460,18 @@ class Configuration {
420460 fields.add ("runtime: $runtime " );
421461 fields.add ("system: $system " );
422462
463+ if (nnbdMode != NnbdMode .legacy) fields.add ("nnbd: $nnbdMode " );
464+
465+ stringListField (String name, List <String > field) {
466+ if (field.isEmpty) return ;
467+ fields.add ("$name : [${field .join (", " )}]" );
468+ }
469+
423470 if (babel.isNotEmpty) fields.add ("babel: $babel " );
424471 if (builderTag.isNotEmpty) fields.add ("builder-tag: $builderTag " );
425- if (vmOptions.isNotEmpty)
426- fields.add ("vm-options: [${vmOptions .join (", " )}]" );
427- if (dart2jsOptions.isNotEmpty)
428- fields.add ("dart2js-options: [${dart2jsOptions .join (", " )}]" );
472+ stringListField ("vm-options" , vmOptions);
473+ stringListField ("dart2js-options" , dart2jsOptions);
474+ stringListField ("experiments" , experiments);
429475 if (timeout > 0 ) fields.add ("timeout: $timeout " );
430476 if (enableAsserts) fields.add ("enable-asserts" );
431477 if (isChecked) fields.add ("checked" );
@@ -456,65 +502,44 @@ class Configuration {
456502 fields.add ("runtime: $runtime ${other .runtime }" );
457503 fields.add ("system: $system ${other .system }" );
458504
459- if (babel.isNotEmpty || other.babel.isNotEmpty) {
460- var ours = babel == "" ? "(none)" : babel;
461- var theirs = other.babel == "" ? "(none)" : other.babel;
462- fields.add ("babel: $ours $theirs " );
463- }
464- if (builderTag.isNotEmpty || other.builderTag.isNotEmpty) {
465- var ours = builderTag == "" ? "(none)" : builderTag;
466- var theirs = other.builderTag == "" ? "(none)" : other.builderTag;
467- fields.add ("builder-tag: $ours $theirs " );
505+ stringField (String name, String value, String otherValue) {
506+ if (value.isEmpty && otherValue.isEmpty) return ;
507+ var ours = value.isEmpty ? "(none)" : value;
508+ var theirs = otherValue.isEmpty ? "(none)" : otherValue;
509+ fields.add ("$name : $ours $theirs " );
468510 }
469- if (vmOptions.isNotEmpty || other.vmOptions.isNotEmpty) {
470- var ours = "[${ vmOptions . join ( ", " )}]" ;
471- var theirs = "[${ other . vmOptions . join ( ", " )}]" ;
472- fields.add ("vm-options: $ ours $ theirs " );
511+
512+ stringListField ( String name, List < String > value, List < String > otherValue) {
513+ if (value.isEmpty && otherValue.isEmpty) return ;
514+ fields.add ("$ name : [${ value . join ( ', ' )}] [${ otherValue . join ( ', ' )}] " );
473515 }
474- if (dart2jsOptions.isNotEmpty || other.dart2jsOptions.isNotEmpty) {
475- var ours = "[${ dart2jsOptions . join ( ", " )}]" ;
476- var theirs = "[${ other . dart2jsOptions . join ( ", " )}]" ;
477- fields.add ("dart2js-options : $ours $ theirs " );
516+
517+ boolField ( String name, bool value, bool otherValue) {
518+ if ( ! value && ! otherValue) return ;
519+ fields.add ("$ name : $value $ otherValue " );
478520 }
521+
522+ fields.add ("nnbd: $nnbdMode ${other .nnbdMode }" );
523+ stringField ("babel" , babel, other.babel);
524+ stringField ("builder-tag" , builderTag, other.builderTag);
525+ stringListField ("vm-options" , vmOptions, other.vmOptions);
526+ stringListField ("dart2js-options" , dart2jsOptions, other.dart2jsOptions);
527+ stringListField ("experiments" , experiments, other.experiments);
479528 fields.add ("timeout: $timeout ${other .timeout }" );
480- if (enableAsserts || other.enableAsserts) {
481- fields.add ("enable-asserts $enableAsserts ${other .enableAsserts }" );
482- }
483- if (isChecked || other.isChecked) {
484- fields.add ("checked $isChecked ${other .isChecked }" );
485- }
486- if (isCsp || other.isCsp) {
487- fields.add ("csp $isCsp ${other .isCsp }" );
488- }
489- if (isHostChecked || other.isHostChecked) {
490- fields.add ("isHostChecked $isHostChecked ${other .isHostChecked }" );
491- }
492- if (isMinified || other.isMinified) {
493- fields.add ("isMinified $isMinified ${other .isMinified }" );
494- }
495- if (useAnalyzerCfe || other.useAnalyzerCfe) {
496- fields.add ("useAnalyzerCfe $useAnalyzerCfe ${other .useAnalyzerCfe }" );
497- }
498- if (useAnalyzerFastaParser || other.useAnalyzerFastaParser) {
499- fields.add ("useAnalyzerFastaParser "
500- "$useAnalyzerFastaParser ${other .useAnalyzerFastaParser }" );
501- }
502- if (useBlobs || other.useBlobs) {
503- fields.add ("useBlobs $useBlobs ${other .useBlobs }" );
504- }
505- if (useHotReload || other.useHotReload) {
506- fields.add ("useHotReload $useHotReload ${other .useHotReload }" );
507- }
508- if (isHostChecked) {
509- fields.add ("host-checked $isHostChecked ${other .isHostChecked }" );
510- }
511- if (useHotReloadRollback || other.useHotReloadRollback) {
512- fields.add ("useHotReloadRollback"
513- " $useHotReloadRollback ${other .useHotReloadRollback }" );
514- }
515- if (useSdk || other.useSdk) {
516- fields.add ("useSdk $useSdk ${other .useSdk }" );
517- }
529+ boolField ("enable-asserts" , enableAsserts, other.enableAsserts);
530+ boolField ("checked" , isChecked, other.isChecked);
531+ boolField ("csp" , isCsp, other.isCsp);
532+ boolField ("host-checked" , isHostChecked, other.isHostChecked);
533+ boolField ("minified" , isMinified, other.isMinified);
534+ boolField ("use-cfe" , useAnalyzerCfe, other.useAnalyzerCfe);
535+ boolField ("analyzer-use-fasta-parser" , useAnalyzerFastaParser,
536+ other.useAnalyzerFastaParser);
537+ boolField ("use-blobs" , useBlobs, other.useBlobs);
538+ boolField ("host-checked" , isHostChecked, other.isHostChecked);
539+ boolField ("hot-reload" , useHotReload, other.useHotReload);
540+ boolField ("hot-reload-rollback" , useHotReloadRollback,
541+ other.useHotReloadRollback);
542+ boolField ("use-sdk" , useSdk, other.useSdk);
518543
519544 buffer.write (fields.join ("\n " ));
520545 buffer.write ("\n " );
@@ -876,6 +901,34 @@ class System extends NamedEnum {
876901 }
877902}
878903
904+ /// What level of non-nullability support should be applied to the test files.
905+ class NnbdMode extends NamedEnum {
906+ /// "Opted out" legacy mode with no NNBD features allowed.
907+ static const legacy = NnbdMode ._('legacy' );
908+
909+ /// Opted in to NNBD features, but only static checking and weak runtime
910+ /// checks.
911+ static const optedIn = NnbdMode ._('opted-in' );
912+
913+ /// Opted in to NNBD features and with full sound runtime checks.
914+ static const strong = NnbdMode ._('strong' );
915+
916+ static final List <String > names = _all.keys.toList ();
917+
918+ static final _all = {
919+ for (var mode in [legacy, optedIn, strong]) mode.name: mode
920+ };
921+
922+ static NnbdMode find (String name) {
923+ var mode = _all[name];
924+ if (mode != null ) return mode;
925+
926+ throw ArgumentError ('Unknown NNBD mode "$name ".' );
927+ }
928+
929+ const NnbdMode ._(String name) : super (name);
930+ }
931+
879932/// Base class for an enum-like class whose values are identified by name.
880933abstract class NamedEnum {
881934 final String name;
0 commit comments