| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dart2js.serialization_test_helper; | 5 library dart2js.serialization_test_helper; |
| 6 | 6 |
| 7 import 'dart:collection'; | 7 import 'dart:collection'; |
| 8 import 'package:compiler/src/common/resolution.dart'; | 8 import 'package:compiler/src/common/resolution.dart'; |
| 9 import 'package:compiler/src/constants/expressions.dart'; | 9 import 'package:compiler/src/constants/expressions.dart'; |
| 10 import 'package:compiler/src/constants/values.dart'; | 10 import 'package:compiler/src/constants/values.dart'; |
| 11 import 'package:compiler/src/compiler.dart'; | 11 import 'package:compiler/src/compiler.dart'; |
| 12 import 'package:compiler/src/elements/elements.dart'; | 12 import 'package:compiler/src/elements/elements.dart'; |
| 13 import 'package:compiler/src/elements/entities.dart'; | 13 import 'package:compiler/src/elements/entities.dart'; |
| 14 import 'package:compiler/src/elements/resolution_types.dart'; | 14 import 'package:compiler/src/elements/resolution_types.dart'; |
| 15 import 'package:compiler/src/elements/types.dart'; | 15 import 'package:compiler/src/elements/types.dart'; |
| 16 import 'package:compiler/src/kernel/elements.dart'; | 16 import 'package:compiler/src/kernel/elements.dart'; |
| 17 import 'package:compiler/src/kernel/element_map_impl.dart'; | 17 import 'package:compiler/src/kernel/element_map_impl.dart'; |
| 18 import 'package:compiler/src/serialization/equivalence.dart'; | 18 import 'package:compiler/src/serialization/equivalence.dart'; |
| 19 import 'package:compiler/src/util/util.dart'; | 19 import 'package:compiler/src/util/util.dart'; |
| 20 import 'package:expect/expect.dart'; | 20 import 'package:expect/expect.dart'; |
| 21 import 'test_data.dart'; | 21 import 'test_data.dart'; |
| 22 | 22 |
| 23 Check currentCheck; | |
| 24 | |
| 25 class Check { | |
| 26 final Check parent; | |
| 27 final Object object1; | |
| 28 final Object object2; | |
| 29 final String property; | |
| 30 final Object value1; | |
| 31 final Object value2; | |
| 32 | |
| 33 Check(this.parent, this.object1, this.object2, this.property, this.value1, | |
| 34 this.value2); | |
| 35 | |
| 36 String printOn(StringBuffer sb, String indent) { | |
| 37 if (parent != null) { | |
| 38 indent = parent.printOn(sb, indent); | |
| 39 sb.write('\n$indent|\n'); | |
| 40 } | |
| 41 sb.write("${indent}property='$property'\n "); | |
| 42 sb.write("${indent}object1=$object1 (${object1.runtimeType})\n "); | |
| 43 sb.write("${indent}value=${value1 == null ? "null" : "'$value1'"} "); | |
| 44 sb.write("(${value1.runtimeType}) vs\n "); | |
| 45 sb.write("${indent}object2=$object2 (${object2.runtimeType})\n "); | |
| 46 sb.write("${indent}value=${value2 == null ? "null" : "'$value2'"} "); | |
| 47 sb.write("(${value2.runtimeType})"); | |
| 48 return ' $indent'; | |
| 49 } | |
| 50 | |
| 51 String toString() { | |
| 52 StringBuffer sb = new StringBuffer(); | |
| 53 printOn(sb, ''); | |
| 54 return sb.toString(); | |
| 55 } | |
| 56 } | |
| 57 | |
| 58 /// Strategy for checking equivalence. | |
| 59 /// | |
| 60 /// Use this strategy to fail early with contextual information in the event of | |
| 61 /// inequivalence. | |
| 62 class CheckStrategy extends TestStrategy { | |
| 63 const CheckStrategy( | |
| 64 {Equivalence<Entity> elementEquivalence: areElementsEquivalent, | |
| 65 Equivalence<DartType> typeEquivalence: areTypesEquivalent, | |
| 66 Equivalence<ConstantExpression> constantEquivalence: | |
| 67 areConstantsEquivalent, | |
| 68 Equivalence<ConstantValue> constantValueEquivalence: | |
| 69 areConstantValuesEquivalent}) | |
| 70 : super( | |
| 71 elementEquivalence: elementEquivalence, | |
| 72 typeEquivalence: typeEquivalence, | |
| 73 constantEquivalence: constantEquivalence, | |
| 74 constantValueEquivalence: constantValueEquivalence); | |
| 75 | |
| 76 TestStrategy get testOnly => new TestStrategy( | |
| 77 elementEquivalence: elementEquivalence, | |
| 78 typeEquivalence: typeEquivalence, | |
| 79 constantEquivalence: constantEquivalence, | |
| 80 constantValueEquivalence: constantValueEquivalence); | |
| 81 | |
| 82 @override | |
| 83 bool test(var object1, var object2, String property, var value1, var value2, | |
| 84 [bool equivalence(a, b) = equality]) { | |
| 85 return check(object1, object2, property, value1, value2, equivalence); | |
| 86 } | |
| 87 | |
| 88 @override | |
| 89 bool testLists( | |
| 90 Object object1, Object object2, String property, List list1, List list2, | |
| 91 [bool elementEquivalence(a, b) = equality]) { | |
| 92 return checkListEquivalence(object1, object2, property, list1, list2, | |
| 93 (o1, o2, p, v1, v2) { | |
| 94 if (!elementEquivalence(v1, v2)) { | |
| 95 throw "$o1.$p = '${v1}' <> " | |
| 96 "$o2.$p = '${v2}'"; | |
| 97 } | |
| 98 return true; | |
| 99 }); | |
| 100 } | |
| 101 | |
| 102 @override | |
| 103 bool testSets( | |
| 104 var object1, var object2, String property, Iterable set1, Iterable set2, | |
| 105 [bool elementEquivalence(a, b) = equality]) { | |
| 106 return checkSetEquivalence( | |
| 107 object1, object2, property, set1, set2, elementEquivalence); | |
| 108 } | |
| 109 | |
| 110 @override | |
| 111 bool testMaps(var object1, var object2, String property, Map map1, Map map2, | |
| 112 [bool keyEquivalence(a, b) = equality, | |
| 113 bool valueEquivalence(a, b) = equality]) { | |
| 114 return checkMapEquivalence(object1, object2, property, map1, map2, | |
| 115 keyEquivalence, valueEquivalence); | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 /// Check that the values [property] of [object1] and [object2], [value1] and | |
| 120 /// [value2] respectively, are equal and throw otherwise. | |
| 121 bool check(var object1, var object2, String property, var value1, var value2, | |
| 122 [bool equivalence(a, b) = equality]) { | |
| 123 currentCheck = | |
| 124 new Check(currentCheck, object1, object2, property, value1, value2); | |
| 125 if (!equivalence(value1, value2)) { | |
| 126 throw currentCheck; | |
| 127 } | |
| 128 currentCheck = currentCheck.parent; | |
| 129 return true; | |
| 130 } | |
| 131 | |
| 132 /// Check equivalence of the two lists, [list1] and [list2], using | |
| 133 /// [checkEquivalence] to check the pair-wise equivalence. | |
| 134 /// | |
| 135 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 136 bool checkListEquivalence( | |
| 137 Object object1, | |
| 138 Object object2, | |
| 139 String property, | |
| 140 Iterable list1, | |
| 141 Iterable list2, | |
| 142 void checkEquivalence(o1, o2, property, a, b)) { | |
| 143 currentCheck = | |
| 144 new Check(currentCheck, object1, object2, property, list1, list2); | |
| 145 for (int i = 0; i < list1.length && i < list2.length; i++) { | |
| 146 checkEquivalence( | |
| 147 object1, object2, property, list1.elementAt(i), list2.elementAt(i)); | |
| 148 } | |
| 149 for (int i = list1.length; i < list2.length; i++) { | |
| 150 throw 'Missing equivalent for element ' | |
| 151 '#$i ${list2.elementAt(i)} in `${property}` on $object2.\n' | |
| 152 '`${property}` on $object1:\n ${list1.join('\n ')}\n' | |
| 153 '`${property}` on $object2:\n ${list2.join('\n ')}'; | |
| 154 } | |
| 155 for (int i = list2.length; i < list1.length; i++) { | |
| 156 throw 'Missing equivalent for element ' | |
| 157 '#$i ${list1.elementAt(i)} in `${property}` on $object1.\n' | |
| 158 '`${property}` on $object1:\n ${list1.join('\n ')}\n' | |
| 159 '`${property}` on $object2:\n ${list2.join('\n ')}'; | |
| 160 } | |
| 161 currentCheck = currentCheck.parent; | |
| 162 return true; | |
| 163 } | |
| 164 | |
| 165 /// Computes the set difference between [set1] and [set2] using | |
| 166 /// [elementEquivalence] to determine element equivalence. | |
| 167 /// | |
| 168 /// Elements both in [set1] and [set2] are added to [common], elements in [set1] | |
| 169 /// but not in [set2] are added to [unfound], and the set of elements in [set2] | |
| 170 /// but not in [set1] are returned. | |
| 171 Set computeSetDifference( | |
| 172 Iterable set1, Iterable set2, List<List> common, List unfound, | |
| 173 {bool sameElement(a, b): equality, void checkElements(a, b)}) { | |
| 174 // TODO(johnniwinther): Avoid the quadratic cost here. Some ideas: | |
| 175 // - convert each set to a list and sort it first, then compare by walking | |
| 176 // both lists in parallel | |
| 177 // - map each element to a canonical object, create a map containing those | |
| 178 // mappings, use the mapped sets to compare (then operations like | |
| 179 // set.difference would work) | |
| 180 Set remaining = set2.toSet(); | |
| 181 for (var element1 in set1) { | |
| 182 bool found = false; | |
| 183 var correspondingElement; | |
| 184 for (var element2 in remaining) { | |
| 185 if (sameElement(element1, element2)) { | |
| 186 if (checkElements != null) { | |
| 187 checkElements(element1, element2); | |
| 188 } | |
| 189 found = true; | |
| 190 correspondingElement = element2; | |
| 191 remaining.remove(element2); | |
| 192 break; | |
| 193 } | |
| 194 } | |
| 195 if (found) { | |
| 196 common.add([element1, correspondingElement]); | |
| 197 } else { | |
| 198 unfound.add(element1); | |
| 199 } | |
| 200 } | |
| 201 return remaining; | |
| 202 } | |
| 203 | |
| 204 /// Check equivalence of the two iterables, [set1] and [set1], as sets using | |
| 205 /// [elementEquivalence] to compute the pair-wise equivalence. | |
| 206 /// | |
| 207 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 208 bool checkSetEquivalence(var object1, var object2, String property, | |
| 209 Iterable set1, Iterable set2, bool sameElement(a, b), | |
| 210 {void onSameElement(a, b)}) { | |
| 211 List<List> common = <List>[]; | |
| 212 List unfound = []; | |
| 213 Set remaining = computeSetDifference(set1, set2, common, unfound, | |
| 214 sameElement: sameElement, checkElements: onSameElement); | |
| 215 if (unfound.isNotEmpty || remaining.isNotEmpty) { | |
| 216 String message = "Set mismatch for `$property` on\n" | |
| 217 "$object1\n vs\n$object2:\n" | |
| 218 "Common:\n ${common.join('\n ')}\n" | |
| 219 "Unfound:\n ${unfound.join('\n ')}\n" | |
| 220 "Extra: \n ${remaining.join('\n ')}"; | |
| 221 throw message; | |
| 222 } | |
| 223 return true; | |
| 224 } | |
| 225 | |
| 226 /// Check equivalence of the two iterables, [set1] and [set1], as sets using | |
| 227 /// [elementEquivalence] to compute the pair-wise equivalence. | |
| 228 /// | |
| 229 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 230 bool checkMapEquivalence(var object1, var object2, String property, Map map1, | |
| 231 Map map2, bool sameKey(a, b), bool sameValue(a, b), | |
| 232 {bool allowExtra: false}) { | |
| 233 List<List> common = <List>[]; | |
| 234 List unfound = []; | |
| 235 Set extra = computeSetDifference(map1.keys, map2.keys, common, unfound, | |
| 236 sameElement: sameKey); | |
| 237 if (unfound.isNotEmpty || (!allowExtra && extra.isNotEmpty)) { | |
| 238 String message = | |
| 239 "Map key mismatch for `$property` on $object1 vs $object2: \n" | |
| 240 "Common:\n ${common.join('\n ')}\n" | |
| 241 "Unfound:\n ${unfound.join('\n ')}\n" | |
| 242 "Extra: \n ${extra.join('\n ')}"; | |
| 243 throw message; | |
| 244 } | |
| 245 for (List pair in common) { | |
| 246 check(pair[0], pair[1], 'Map value for `$property`', map1[pair[0]], | |
| 247 map2[pair[1]], sameValue); | |
| 248 } | |
| 249 return true; | |
| 250 } | |
| 251 | |
| 252 /// Checks the equivalence of the identity (but not properties) of [element1] | |
| 253 /// and [element2]. | |
| 254 /// | |
| 255 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 256 bool checkElementIdentities(Object object1, Object object2, String property, | |
| 257 Element element1, Element element2) { | |
| 258 if (identical(element1, element2)) return true; | |
| 259 return check( | |
| 260 object1, object2, property, element1, element2, areElementsEquivalent); | |
| 261 } | |
| 262 | |
| 263 /// Checks the pair-wise equivalence of the identity (but not properties) of the | |
| 264 /// elements in [list] and [list2]. | |
| 265 /// | |
| 266 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 267 bool checkElementListIdentities(Object object1, Object object2, String property, | |
| 268 Iterable<Element> list1, Iterable<Element> list2) { | |
| 269 return checkListEquivalence( | |
| 270 object1, object2, property, list1, list2, checkElementIdentities); | |
| 271 } | |
| 272 | |
| 273 /// Checks the equivalence of [type1] and [type2]. | |
| 274 /// | |
| 275 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 276 bool checkTypes(Object object1, Object object2, String property, | |
| 277 ResolutionDartType type1, ResolutionDartType type2) { | |
| 278 if (identical(type1, type2)) return true; | |
| 279 if (type1 == null || type2 == null) { | |
| 280 return check(object1, object2, property, type1, type2); | |
| 281 } else { | |
| 282 return check(object1, object2, property, type1, type2, | |
| 283 (a, b) => const TypeEquivalence(const CheckStrategy()).visit(a, b)); | |
| 284 } | |
| 285 } | |
| 286 | |
| 287 /// Checks the pair-wise equivalence of the types in [list1] and [list2]. | |
| 288 /// | |
| 289 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 290 bool checkTypeLists(Object object1, Object object2, String property, | |
| 291 List<DartType> list1, List<DartType> list2) { | |
| 292 return checkListEquivalence( | |
| 293 object1, object2, property, list1, list2, checkTypes); | |
| 294 } | |
| 295 | |
| 296 /// Checks the equivalence of [exp1] and [exp2]. | |
| 297 /// | |
| 298 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 299 bool checkConstants(Object object1, Object object2, String property, | |
| 300 ConstantExpression exp1, ConstantExpression exp2) { | |
| 301 if (identical(exp1, exp2)) return true; | |
| 302 if (exp1 == null || exp2 == null) { | |
| 303 return check(object1, object2, property, exp1, exp2); | |
| 304 } else { | |
| 305 return check(object1, object2, property, exp1, exp2, | |
| 306 (a, b) => const ConstantEquivalence(const CheckStrategy()).visit(a, b)); | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 /// Checks the equivalence of [value1] and [value2]. | |
| 311 /// | |
| 312 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 313 bool checkConstantValues(Object object1, Object object2, String property, | |
| 314 ConstantValue value1, ConstantValue value2) { | |
| 315 if (identical(value1, value2)) return true; | |
| 316 if (value1 == null || value2 == null) { | |
| 317 return check(object1, object2, property, value1, value2); | |
| 318 } else { | |
| 319 return check( | |
| 320 object1, | |
| 321 object2, | |
| 322 property, | |
| 323 value1, | |
| 324 value2, | |
| 325 (a, b) => | |
| 326 const ConstantValueEquivalence(const CheckStrategy()).visit(a, b)); | |
| 327 } | |
| 328 } | |
| 329 | |
| 330 /// Checks the pair-wise equivalence of the constants in [list1] and [list2]. | |
| 331 /// | |
| 332 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 333 bool checkConstantLists(Object object1, Object object2, String property, | |
| 334 List<ConstantExpression> list1, List<ConstantExpression> list2) { | |
| 335 return checkListEquivalence( | |
| 336 object1, object2, property, list1, list2, checkConstants); | |
| 337 } | |
| 338 | |
| 339 /// Checks the pair-wise equivalence of the constants values in [list1] and | |
| 340 /// [list2]. | |
| 341 /// | |
| 342 /// Uses [object1], [object2] and [property] to provide context for failures. | |
| 343 bool checkConstantValueLists(Object object1, Object object2, String property, | |
| 344 List<ConstantValue> list1, List<ConstantValue> list2) { | |
| 345 return checkListEquivalence( | |
| 346 object1, object2, property, list1, list2, checkConstantValues); | |
| 347 } | |
| 348 | |
| 349 /// Check member property equivalence between all members common to [compiler1] | |
| 350 /// and [compiler2]. | |
| 351 void checkLoadedLibraryMembers( | |
| 352 Compiler compiler1, | |
| 353 Compiler compiler2, | |
| 354 bool hasProperty(Element member1), | |
| 355 void checkMemberProperties(Compiler compiler1, Element member1, | |
| 356 Compiler compiler2, Element member2, | |
| 357 {bool verbose}), | |
| 358 {bool verbose: false}) { | |
| 359 void checkMembers(Element member1, Element member2) { | |
| 360 if (member1.isClass && member2.isClass) { | |
| 361 ClassElement class1 = member1; | |
| 362 ClassElement class2 = member2; | |
| 363 if (!class1.isResolved) return; | |
| 364 | |
| 365 if (hasProperty(member1)) { | |
| 366 if (areElementsEquivalent(member1, member2)) { | |
| 367 checkMemberProperties(compiler1, member1, compiler2, member2, | |
| 368 verbose: verbose); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 class1.forEachLocalMember((m1) { | |
| 373 checkMembers(m1, class2.localLookup(m1.name)); | |
| 374 }); | |
| 375 ClassElement superclass1 = class1.superclass; | |
| 376 ClassElement superclass2 = class2.superclass; | |
| 377 while (superclass1 != null && superclass1.isUnnamedMixinApplication) { | |
| 378 for (ConstructorElement c1 in superclass1.constructors) { | |
| 379 checkMembers(c1, superclass2.lookupConstructor(c1.name)); | |
| 380 } | |
| 381 superclass1 = superclass1.superclass; | |
| 382 superclass2 = superclass2.superclass; | |
| 383 } | |
| 384 return; | |
| 385 } | |
| 386 | |
| 387 if (!hasProperty(member1)) { | |
| 388 return; | |
| 389 } | |
| 390 | |
| 391 if (member2 == null) { | |
| 392 throw 'Missing member for ${member1}'; | |
| 393 } | |
| 394 | |
| 395 if (areElementsEquivalent(member1, member2)) { | |
| 396 checkMemberProperties(compiler1, member1, compiler2, member2, | |
| 397 verbose: verbose); | |
| 398 } | |
| 399 } | |
| 400 | |
| 401 for (LibraryElement library1 in compiler1.libraryLoader.libraries) { | |
| 402 LibraryElement library2 = | |
| 403 compiler2.libraryLoader.lookupLibrary(library1.canonicalUri); | |
| 404 if (library2 != null) { | |
| 405 library1.forEachLocalMember((Element member1) { | |
| 406 checkMembers(member1, library2.localLookup(member1.name)); | |
| 407 }); | |
| 408 } | |
| 409 } | |
| 410 } | |
| 411 | |
| 412 /// Check equivalence of all resolution impacts. | |
| 413 void checkAllImpacts(Compiler compiler1, Compiler compiler2, | |
| 414 {bool verbose: false}) { | |
| 415 checkLoadedLibraryMembers(compiler1, compiler2, (Element member1) { | |
| 416 return compiler1.resolution.hasResolutionImpact(member1); | |
| 417 }, checkImpacts, verbose: verbose); | |
| 418 } | |
| 419 | |
| 420 /// Check equivalence of resolution impact for [member1] and [member2]. | |
| 421 void checkImpacts( | |
| 422 Compiler compiler1, Element member1, Compiler compiler2, Element member2, | |
| 423 {bool verbose: false}) { | |
| 424 ResolutionImpact impact1 = compiler1.resolution.getResolutionImpact(member1); | |
| 425 ResolutionImpact impact2 = compiler2.resolution.getResolutionImpact(member2); | |
| 426 | |
| 427 if (impact1 == null && impact2 == null) return; | |
| 428 | |
| 429 if (verbose) { | |
| 430 print('Checking impacts for $member1 vs $member2'); | |
| 431 } | |
| 432 | |
| 433 if (impact1 == null) { | |
| 434 throw 'Missing impact for $member1. $member2 has $impact2'; | |
| 435 } | |
| 436 if (impact2 == null) { | |
| 437 throw 'Missing impact for $member2. $member1 has $impact1'; | |
| 438 } | |
| 439 | |
| 440 testResolutionImpactEquivalence(impact1, impact2, | |
| 441 strategy: const CheckStrategy()); | |
| 442 } | |
| 443 | |
| 444 void checkSets( | |
| 445 Iterable set1, Iterable set2, String messagePrefix, bool sameElement(a, b), | |
| 446 {bool failOnUnfound: true, | |
| 447 bool failOnExtra: true, | |
| 448 bool verbose: false, | |
| 449 void onSameElement(a, b), | |
| 450 void onUnfoundElement(a), | |
| 451 void onExtraElement(b), | |
| 452 bool elementFilter(element), | |
| 453 elementConverter(element), | |
| 454 String elementToString(key): defaultToString}) { | |
| 455 if (elementFilter != null) { | |
| 456 set1 = set1.where(elementFilter); | |
| 457 set2 = set2.where(elementFilter); | |
| 458 } | |
| 459 if (elementConverter != null) { | |
| 460 set1 = set1.map(elementConverter); | |
| 461 set2 = set2.map(elementConverter); | |
| 462 } | |
| 463 List<List> common = <List>[]; | |
| 464 List unfound = []; | |
| 465 Set remaining = computeSetDifference(set1, set2, common, unfound, | |
| 466 sameElement: sameElement, checkElements: onSameElement); | |
| 467 if (onUnfoundElement != null) { | |
| 468 unfound.forEach(onUnfoundElement); | |
| 469 } | |
| 470 if (onExtraElement != null) { | |
| 471 remaining.forEach(onExtraElement); | |
| 472 } | |
| 473 StringBuffer sb = new StringBuffer(); | |
| 474 sb.write("$messagePrefix:"); | |
| 475 if (verbose) { | |
| 476 sb.write("\n Common: \n"); | |
| 477 for (List pair in common) { | |
| 478 var element1 = pair[0]; | |
| 479 var element2 = pair[1]; | |
| 480 sb.write(" [${elementToString(element1)}," | |
| 481 "${elementToString(element2)}]\n"); | |
| 482 } | |
| 483 } | |
| 484 if (unfound.isNotEmpty || verbose) { | |
| 485 sb.write("\n Unfound:\n ${unfound.map(elementToString).join('\n ')}"); | |
| 486 } | |
| 487 if (remaining.isNotEmpty || verbose) { | |
| 488 sb.write("\n Extra: \n ${remaining.map(elementToString).join('\n ')}"); | |
| 489 } | |
| 490 String message = sb.toString(); | |
| 491 if (unfound.isNotEmpty || remaining.isNotEmpty) { | |
| 492 if ((failOnUnfound && unfound.isNotEmpty) || | |
| 493 (failOnExtra && remaining.isNotEmpty)) { | |
| 494 Expect.fail(message); | |
| 495 } else { | |
| 496 print(message); | |
| 497 } | |
| 498 } else if (verbose) { | |
| 499 print(message); | |
| 500 } | |
| 501 } | |
| 502 | |
| 503 String defaultToString(obj) => '$obj'; | |
| 504 | |
| 505 void checkMaps(Map map1, Map map2, String messagePrefix, bool sameKey(a, b), | |
| 506 bool sameValue(a, b), | |
| 507 {bool failOnUnfound: true, | |
| 508 bool failOnMismatch: true, | |
| 509 bool verbose: false, | |
| 510 String keyToString(key): defaultToString, | |
| 511 String valueToString(key): defaultToString}) { | |
| 512 List<List> common = <List>[]; | |
| 513 List unfound = []; | |
| 514 List<List> mismatch = <List>[]; | |
| 515 Set remaining = computeSetDifference(map1.keys, map2.keys, common, unfound, | |
| 516 sameElement: sameKey, checkElements: (k1, k2) { | |
| 517 var v1 = map1[k1]; | |
| 518 var v2 = map2[k2]; | |
| 519 if (!sameValue(v1, v2)) { | |
| 520 mismatch.add([k1, k2]); | |
| 521 } | |
| 522 }); | |
| 523 StringBuffer sb = new StringBuffer(); | |
| 524 sb.write("$messagePrefix:"); | |
| 525 if (verbose) { | |
| 526 sb.write("\n Common: \n"); | |
| 527 for (List pair in common) { | |
| 528 var k1 = pair[0]; | |
| 529 var k2 = pair[1]; | |
| 530 var v1 = map1[k1]; | |
| 531 var v2 = map2[k2]; | |
| 532 sb.write(" key1 =${keyToString(k1)}\n"); | |
| 533 sb.write(" key2 =${keyToString(k2)}\n"); | |
| 534 sb.write(" value1=${valueToString(v1)}\n"); | |
| 535 sb.write(" value2=${valueToString(v2)}\n"); | |
| 536 } | |
| 537 } | |
| 538 if (unfound.isNotEmpty || verbose) { | |
| 539 sb.write("\n Unfound: \n"); | |
| 540 for (var k1 in unfound) { | |
| 541 var v1 = map1[k1]; | |
| 542 sb.write(" key1 =${keyToString(k1)}\n"); | |
| 543 sb.write(" value1=${valueToString(v1)}\n"); | |
| 544 } | |
| 545 } | |
| 546 if (remaining.isNotEmpty || verbose) { | |
| 547 sb.write("\n Extra: \n"); | |
| 548 for (var k2 in remaining) { | |
| 549 var v2 = map2[k2]; | |
| 550 sb.write(" key2 =${keyToString(k2)}\n"); | |
| 551 sb.write(" value2=${valueToString(v2)}\n"); | |
| 552 } | |
| 553 } | |
| 554 if (mismatch.isNotEmpty || verbose) { | |
| 555 sb.write("\n Mismatch: \n"); | |
| 556 for (List pair in mismatch) { | |
| 557 var k1 = pair[0]; | |
| 558 var k2 = pair[1]; | |
| 559 var v1 = map1[k1]; | |
| 560 var v2 = map2[k2]; | |
| 561 sb.write(" key1 =${keyToString(k1)}\n"); | |
| 562 sb.write(" key2 =${keyToString(k2)}\n"); | |
| 563 sb.write(" value1=${valueToString(v1)}\n"); | |
| 564 sb.write(" value2=${valueToString(v2)}\n"); | |
| 565 } | |
| 566 } | |
| 567 String message = sb.toString(); | |
| 568 if (unfound.isNotEmpty || mismatch.isNotEmpty || remaining.isNotEmpty) { | |
| 569 if ((unfound.isNotEmpty && failOnUnfound) || | |
| 570 (mismatch.isNotEmpty && failOnMismatch) || | |
| 571 remaining.isNotEmpty) { | |
| 572 Expect.fail(message); | |
| 573 } else { | |
| 574 print(message); | |
| 575 } | |
| 576 } else if (verbose) { | |
| 577 print(message); | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 void checkAllResolvedAsts(Compiler compiler1, Compiler compiler2, | |
| 582 {bool verbose: false}) { | |
| 583 checkLoadedLibraryMembers(compiler1, compiler2, (Element member1) { | |
| 584 return member1 is ExecutableElement && | |
| 585 compiler1.resolution.hasResolvedAst(member1); | |
| 586 }, checkResolvedAsts, verbose: verbose); | |
| 587 } | |
| 588 | |
| 589 /// Check equivalence of [impact1] and [impact2]. | |
| 590 void checkResolvedAsts( | |
| 591 Compiler compiler1, Element member1, Compiler compiler2, Element member2, | |
| 592 {bool verbose: false}) { | |
| 593 if (!compiler2.serialization.isDeserialized(member2)) { | |
| 594 return; | |
| 595 } | |
| 596 ResolvedAst resolvedAst1 = compiler1.resolution.getResolvedAst(member1); | |
| 597 ResolvedAst resolvedAst2 = compiler2.serialization.getResolvedAst(member2); | |
| 598 | |
| 599 if (resolvedAst1 == null || resolvedAst2 == null) return; | |
| 600 | |
| 601 if (verbose) { | |
| 602 print('Checking resolved asts for $member1 vs $member2'); | |
| 603 } | |
| 604 | |
| 605 testResolvedAstEquivalence(resolvedAst1, resolvedAst2, const CheckStrategy()); | |
| 606 } | |
| 607 | |
| 608 /// Returns the test arguments for testing the [index]th skipped test. The | 23 /// Returns the test arguments for testing the [index]th skipped test. The |
| 609 /// [skip] count is used to check that [index] is a valid index. | 24 /// [skip] count is used to check that [index] is a valid index. |
| 610 List<String> testSkipped(int index, int skip) { | 25 List<String> testSkipped(int index, int skip) { |
| 611 if (index < 0 || index >= skip) { | 26 if (index < 0 || index >= skip) { |
| 612 throw new ArgumentError('Invalid skip index $index'); | 27 throw new ArgumentError('Invalid skip index $index'); |
| 613 } | 28 } |
| 614 return ['${index}', '${index + 1}']; | 29 return ['${index}', '${index + 1}']; |
| 615 } | 30 } |
| 616 | 31 |
| 617 /// Return the test arguments for testing the [index]th segment (1-based) of | 32 /// Return the test arguments for testing the [index]th segment (1-based) of |
| 618 /// the [TESTS] split into [count] groups. The first [skip] tests are excluded | 33 /// the [TESTS] split into [count] groups. The first [skip] tests are excluded |
| 619 /// from the automatic grouping. | 34 /// from the automatic grouping. |
| 620 List<String> testSegment(int index, int count, int skip) { | 35 List<String> testSegment(int index, int count, int skip) { |
| 621 if (index < 0 || index > count) { | 36 if (index < 0 || index > count) { |
| 622 throw new ArgumentError('Invalid segment index $index'); | 37 throw new ArgumentError('Invalid segment index $index'); |
| 623 } | 38 } |
| 624 | 39 |
| 625 String segmentNumber(int i) { | 40 String segmentNumber(int i) { |
| 626 return '${skip + i * (TESTS.length - skip) ~/ count}'; | 41 return '${skip + i * (TESTS.length - skip) ~/ count}'; |
| 627 } | 42 } |
| 628 | 43 |
| 629 if (index == 1 && skip != 0) { | 44 if (index == 1 && skip != 0) { |
| 630 return ['${skip}', segmentNumber(index)]; | 45 return ['${skip}', segmentNumber(index)]; |
| 631 } else if (index == count) { | 46 } else if (index == count) { |
| 632 return [segmentNumber(index - 1)]; | 47 return [segmentNumber(index - 1)]; |
| 633 } else { | 48 } else { |
| 634 return [segmentNumber(index - 1), segmentNumber(index)]; | 49 return [segmentNumber(index - 1), segmentNumber(index)]; |
| 635 } | 50 } |
| 636 } | 51 } |
| 637 | |
| 638 class KernelEquivalence { | |
| 639 final WorldDeconstructionForTesting testing; | |
| 640 | |
| 641 /// Set of mixin applications assumed to be equivalent. | |
| 642 /// | |
| 643 /// We need co-inductive reasoning because mixin applications are compared | |
| 644 /// structurally and therefore, in the case of generic mixin applications, | |
| 645 /// meet themselves through the equivalence check of their type variables. | |
| 646 Set<Pair<ClassEntity, ClassEntity>> assumedMixinApplications = | |
| 647 new Set<Pair<ClassEntity, ClassEntity>>(); | |
| 648 | |
| 649 KernelEquivalence(KernelToElementMapImpl builder) | |
| 650 : testing = new WorldDeconstructionForTesting(builder); | |
| 651 | |
| 652 TestStrategy get defaultStrategy => new TestStrategy( | |
| 653 elementEquivalence: entityEquivalence, | |
| 654 typeEquivalence: typeEquivalence, | |
| 655 constantEquivalence: constantEquivalence, | |
| 656 constantValueEquivalence: constantValueEquivalence); | |
| 657 | |
| 658 bool entityEquivalence(Element a, Entity b, {TestStrategy strategy}) { | |
| 659 if (identical(a, b)) return true; | |
| 660 if (a == null || b == null) return false; | |
| 661 strategy ??= defaultStrategy; | |
| 662 switch (a.kind) { | |
| 663 case ElementKind.GENERATIVE_CONSTRUCTOR: | |
| 664 if (b is KGenerativeConstructor) { | |
| 665 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 666 strategy.testElements( | |
| 667 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass); | |
| 668 } | |
| 669 return false; | |
| 670 case ElementKind.FACTORY_CONSTRUCTOR: | |
| 671 if (b is KFactoryConstructor) { | |
| 672 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 673 strategy.testElements( | |
| 674 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass); | |
| 675 } | |
| 676 return false; | |
| 677 case ElementKind.CLASS: | |
| 678 if (b is KClass) { | |
| 679 List<InterfaceType> aMixinTypes = []; | |
| 680 List<InterfaceType> bMixinTypes = []; | |
| 681 ClassElement aClass = a; | |
| 682 if (aClass.isUnnamedMixinApplication) { | |
| 683 if (!testing.isUnnamedMixinApplication(b)) { | |
| 684 return false; | |
| 685 } | |
| 686 while (aClass.isMixinApplication) { | |
| 687 MixinApplicationElement aMixinApplication = aClass; | |
| 688 aMixinTypes.add(aMixinApplication.mixinType); | |
| 689 aClass = aMixinApplication.superclass; | |
| 690 } | |
| 691 KClass bClass = b; | |
| 692 while (bClass != null) { | |
| 693 InterfaceType mixinType = testing.getMixinTypeForClass(bClass); | |
| 694 if (mixinType == null) break; | |
| 695 bMixinTypes.add(mixinType); | |
| 696 bClass = testing.getSuperclassForClass(bClass); | |
| 697 } | |
| 698 if (aMixinTypes.isNotEmpty || aMixinTypes.isNotEmpty) { | |
| 699 Pair<ClassEntity, ClassEntity> pair = | |
| 700 new Pair<ClassEntity, ClassEntity>(aClass, bClass); | |
| 701 if (assumedMixinApplications.contains(pair)) { | |
| 702 return true; | |
| 703 } else { | |
| 704 assumedMixinApplications.add(pair); | |
| 705 bool result = strategy.testTypeLists( | |
| 706 a, b, 'mixinTypes', aMixinTypes, bMixinTypes); | |
| 707 assumedMixinApplications.remove(pair); | |
| 708 return result; | |
| 709 } | |
| 710 } | |
| 711 } else { | |
| 712 if (testing.isUnnamedMixinApplication(b)) { | |
| 713 return false; | |
| 714 } | |
| 715 } | |
| 716 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 717 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 718 } | |
| 719 return false; | |
| 720 case ElementKind.LIBRARY: | |
| 721 if (b is KLibrary) { | |
| 722 LibraryElement libraryA = a; | |
| 723 return libraryA.canonicalUri == b.canonicalUri; | |
| 724 } | |
| 725 return false; | |
| 726 case ElementKind.FUNCTION: | |
| 727 if (b is KMethod) { | |
| 728 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 729 strategy.testElements( | |
| 730 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 731 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 732 } else if (b is KLocalFunction) { | |
| 733 LocalFunctionElement aLocalFunction = a; | |
| 734 return strategy.test(a, b, 'name', a.name, b.name ?? '') && | |
| 735 strategy.testElements(a, b, 'executableContext', | |
| 736 aLocalFunction.executableContext, b.executableContext) && | |
| 737 strategy.testElements(a, b, 'memberContext', | |
| 738 aLocalFunction.memberContext, b.memberContext); | |
| 739 } | |
| 740 return false; | |
| 741 case ElementKind.GETTER: | |
| 742 if (b is KGetter) { | |
| 743 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 744 strategy.testElements( | |
| 745 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 746 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 747 } | |
| 748 return false; | |
| 749 case ElementKind.SETTER: | |
| 750 if (b is KSetter) { | |
| 751 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 752 strategy.testElements( | |
| 753 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 754 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 755 } | |
| 756 return false; | |
| 757 case ElementKind.FIELD: | |
| 758 if (b is KField) { | |
| 759 return strategy.test(a, b, 'name', a.name, b.name) && | |
| 760 strategy.testElements( | |
| 761 a, b, 'enclosingClass', a.enclosingClass, b.enclosingClass) && | |
| 762 strategy.testElements(a, b, 'library', a.library, b.library); | |
| 763 } | |
| 764 return false; | |
| 765 case ElementKind.TYPE_VARIABLE: | |
| 766 if (b is KTypeVariable) { | |
| 767 TypeVariableElement aElement = a; | |
| 768 return strategy.test(a, b, 'index', aElement.index, b.index) && | |
| 769 strategy.testElements(a, b, 'typeDeclaration', | |
| 770 aElement.typeDeclaration, b.typeDeclaration); | |
| 771 } | |
| 772 return false; | |
| 773 default: | |
| 774 throw new UnsupportedError('Unsupported equivalence: ' | |
| 775 '$a (${a.runtimeType}) vs $b (${b.runtimeType})'); | |
| 776 } | |
| 777 } | |
| 778 | |
| 779 bool typeEquivalence(ResolutionDartType a, DartType b, | |
| 780 {TestStrategy strategy}) { | |
| 781 if (identical(a, b)) return true; | |
| 782 if (a == null || b == null) return false; | |
| 783 strategy ??= defaultStrategy; | |
| 784 switch (a.kind) { | |
| 785 case ResolutionTypeKind.DYNAMIC: | |
| 786 return b is DynamicType; | |
| 787 case ResolutionTypeKind.VOID: | |
| 788 return b is VoidType; | |
| 789 case ResolutionTypeKind.INTERFACE: | |
| 790 if (b is InterfaceType) { | |
| 791 ResolutionInterfaceType aType = a; | |
| 792 return strategy.testElements(a, b, 'element', a.element, b.element) && | |
| 793 strategy.testTypeLists( | |
| 794 a, b, 'typeArguments', aType.typeArguments, b.typeArguments); | |
| 795 } | |
| 796 return false; | |
| 797 case ResolutionTypeKind.TYPE_VARIABLE: | |
| 798 if (b is TypeVariableType) { | |
| 799 return strategy.testElements(a, b, 'element', a.element, b.element); | |
| 800 } | |
| 801 return false; | |
| 802 case ResolutionTypeKind.FUNCTION: | |
| 803 if (b is FunctionType) { | |
| 804 ResolutionFunctionType aType = a; | |
| 805 return strategy.testTypes( | |
| 806 a, b, 'returnType', aType.returnType, b.returnType) && | |
| 807 strategy.testTypeLists(a, b, 'parameterTypes', | |
| 808 aType.parameterTypes, b.parameterTypes) && | |
| 809 strategy.testTypeLists(a, b, 'optionalParameterTypes', | |
| 810 aType.optionalParameterTypes, b.optionalParameterTypes) && | |
| 811 strategy.testLists(a, b, 'namedParameters', aType.namedParameters, | |
| 812 b.namedParameters) && | |
| 813 strategy.testTypeLists(a, b, 'namedParameterTypes', | |
| 814 aType.namedParameterTypes, b.namedParameterTypes); | |
| 815 } | |
| 816 return false; | |
| 817 default: | |
| 818 throw new UnsupportedError('Unsupported equivalence: ' | |
| 819 '$a (${a.runtimeType}) vs $b (${b.runtimeType})'); | |
| 820 } | |
| 821 } | |
| 822 | |
| 823 bool constantEquivalence(ConstantExpression exp1, ConstantExpression exp2, | |
| 824 {TestStrategy strategy}) { | |
| 825 strategy ??= defaultStrategy; | |
| 826 return areConstantsEquivalent(exp1, exp2, strategy: strategy); | |
| 827 } | |
| 828 | |
| 829 bool constantValueEquivalence(ConstantValue value1, ConstantValue value2, | |
| 830 {TestStrategy strategy}) { | |
| 831 strategy ??= defaultStrategy; | |
| 832 return areConstantValuesEquivalent(value1, value2, strategy: strategy); | |
| 833 } | |
| 834 } | |
| OLD | NEW |