ExprModelTest.java revision fead9ca09b117136b35bc5bf137340a754f9eddd
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.databinding.tool.expr; 18 19import com.google.common.base.Predicate; 20import com.google.common.collect.Iterables; 21 22import org.apache.commons.lang3.ArrayUtils; 23import org.apache.commons.lang3.NotImplementedException; 24import org.junit.Before; 25import org.junit.Rule; 26import org.junit.Test; 27import org.junit.rules.TestWatcher; 28import org.junit.runner.Description; 29 30import android.databinding.tool.LayoutBinder; 31import android.databinding.tool.MockLayoutBinder; 32import android.databinding.tool.reflection.ModelAnalyzer; 33import android.databinding.tool.reflection.ModelClass; 34import android.databinding.tool.reflection.java.JavaAnalyzer; 35import android.databinding.tool.util.L; 36 37import java.util.BitSet; 38import java.util.List; 39 40import static org.junit.Assert.assertEquals; 41import static org.junit.Assert.assertFalse; 42import static org.junit.Assert.assertNotNull; 43import static org.junit.Assert.assertNull; 44import static org.junit.Assert.assertSame; 45import static org.junit.Assert.assertTrue; 46 47public class ExprModelTest { 48 private static class DummyExpr extends Expr { 49 String mKey; 50 public DummyExpr(String key, DummyExpr... children) { 51 super(children); 52 mKey = key; 53 } 54 55 @Override 56 protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { 57 return modelAnalyzer.findClass(Integer.class); 58 } 59 60 @Override 61 protected List<Dependency> constructDependencies() { 62 return constructDynamicChildrenDependencies(); 63 } 64 65 @Override 66 protected String computeUniqueKey() { 67 return mKey + super.computeUniqueKey(); 68 } 69 } 70 71 ExprModel mExprModel; 72 73 @Rule 74 public TestWatcher mTestWatcher = new TestWatcher() { 75 @Override 76 protected void failed(Throwable e, Description description) { 77 if (mExprModel != null && mExprModel.getFlagMapping() != null) { 78 final String[] mapping = mExprModel.getFlagMapping(); 79 for (int i = 0; i < mapping.length; i ++) { 80 L.d("flag %d: %s", i, mapping[i]); 81 } 82 } 83 } 84 }; 85 86 @Before 87 public void setUp() throws Exception { 88 JavaAnalyzer.initForTests(); 89 mExprModel = new ExprModel(); 90 } 91 92 @Test 93 public void testAddNormal() { 94 final DummyExpr d = new DummyExpr("a"); 95 assertSame(d, mExprModel.register(d)); 96 assertSame(d, mExprModel.register(d)); 97 assertEquals(1, mExprModel.mExprMap.size()); 98 } 99 @Test 100 public void testAddDupe1() { 101 final DummyExpr d = new DummyExpr("a"); 102 assertSame(d, mExprModel.register(d)); 103 assertSame(d, mExprModel.register(new DummyExpr("a"))); 104 assertEquals(1, mExprModel.mExprMap.size()); 105 } 106 107 @Test 108 public void testAddMultiple() { 109 mExprModel.register(new DummyExpr("a")); 110 mExprModel.register(new DummyExpr("b")); 111 assertEquals(2, mExprModel.mExprMap.size()); 112 } 113 114 115 @Test 116 public void testAddWithChildren() { 117 DummyExpr a = new DummyExpr("a"); 118 DummyExpr b = new DummyExpr("b"); 119 DummyExpr c = new DummyExpr("c", a, b); 120 mExprModel.register(c); 121 DummyExpr a2 = new DummyExpr("a"); 122 DummyExpr b2 = new DummyExpr("b"); 123 DummyExpr c2 = new DummyExpr("c", a, b); 124 assertEquals(c, mExprModel.register(c2)); 125 } 126 127 @Test 128 public void testShouldRead() { 129 LayoutBinder lb = new MockLayoutBinder(); 130 mExprModel = lb.getModel(); 131 IdentifierExpr a = lb.addVariable("a", "java.lang.String"); 132 IdentifierExpr b = lb.addVariable("b", "java.lang.String"); 133 IdentifierExpr c = lb.addVariable("c", "java.lang.String"); 134 final Expr ternary = lb.parse("a == null ? b : c"); 135 final Expr equality = mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class)); 136 lb.getModel().seal(); 137 Iterable<Expr> shouldRead = getShouldRead(); 138 // a and a == null 139 assertEquals(2, Iterables.size(shouldRead)); 140 final Iterable<Expr> readFirst = getReadFirst(shouldRead, null); 141 assertEquals(1, Iterables.size(readFirst)); 142 final Expr first = Iterables.getFirst(readFirst, null); 143 assertSame(a, first); 144 // now , assume we've read this 145 final BitSet shouldReadFlags = first.getShouldReadFlags(); 146 assertNotNull(shouldReadFlags); 147 } 148 149 @Test 150 public void testTernaryInsideTernary() { 151 LayoutBinder lb = new MockLayoutBinder(); 152 mExprModel = lb.getModel(); 153 IdentifierExpr cond1 = lb.addVariable("cond1", "boolean"); 154 IdentifierExpr cond2 = lb.addVariable("cond2", "boolean"); 155 156 IdentifierExpr a = lb.addVariable("a", "boolean"); 157 IdentifierExpr b = lb.addVariable("b", "boolean"); 158 IdentifierExpr c = lb.addVariable("c", "boolean"); 159 160 final TernaryExpr ternaryExpr = parse(lb, "cond1 ? cond2 ? a : b : c", TernaryExpr.class); 161 final TernaryExpr innerTernary = (TernaryExpr) ternaryExpr.getIfTrue(); 162 mExprModel.seal(); 163 164 Iterable<Expr> toRead = getShouldRead(); 165 assertEquals(1, Iterables.size(toRead)); 166 assertEquals(ternaryExpr.getPred(), Iterables.getFirst(toRead, null)); 167 168 Iterable<Expr> readNow = getReadFirst(toRead); 169 assertEquals(1, Iterables.size(readNow)); 170 assertEquals(ternaryExpr.getPred(), Iterables.getFirst(readNow, null)); 171 int cond1True = ternaryExpr.getRequirementFlagIndex(true); 172 int cond1False = ternaryExpr.getRequirementFlagIndex(false); 173 // ok, it is read now. 174 mExprModel.markBitsRead(); 175 176 // now it should read cond2 or c, depending on the flag from first 177 toRead = getShouldRead(); 178 assertEquals(2, Iterables.size(toRead)); 179 assertExactMatch(toRead, ternaryExpr.getIfFalse(), innerTernary.getPred()); 180 assertFlags(ternaryExpr.getIfFalse(), cond1False); 181 assertFlags(ternaryExpr.getIfTrue(), cond1True); 182 183 mExprModel.markBitsRead(); 184 185 // now it should read a or b, innerTernary, outerTernary 186 toRead = getShouldRead(); 187 assertExactMatch(toRead, innerTernary.getIfTrue(), innerTernary.getIfFalse(), ternaryExpr, 188 innerTernary); 189 assertFlags(innerTernary.getIfTrue(), innerTernary.getRequirementFlagIndex(true)); 190 assertFlags(innerTernary.getIfFalse(), innerTernary.getRequirementFlagIndex(false)); 191 assertFalse(mExprModel.markBitsRead()); 192 } 193 194 @Test 195 public void testRequirementFlags() { 196 LayoutBinder lb = new MockLayoutBinder(); 197 mExprModel = lb.getModel(); 198 IdentifierExpr a = lb.addVariable("a", "java.lang.String"); 199 IdentifierExpr b = lb.addVariable("b", "java.lang.String"); 200 IdentifierExpr c = lb.addVariable("c", "java.lang.String"); 201 IdentifierExpr d = lb.addVariable("d", "java.lang.String"); 202 IdentifierExpr e = lb.addVariable("e", "java.lang.String"); 203 final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e"); 204 assertTrue(aTernary instanceof TernaryExpr); 205 final Expr bTernary = ((TernaryExpr)aTernary).getIfTrue(); 206 assertTrue(bTernary instanceof TernaryExpr); 207 final Expr aIsNull = mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class)); 208 final Expr bIsNull = mExprModel.comparison("==", b, mExprModel.symbol("null", Object.class)); 209 lb.getModel().seal(); 210 Iterable<Expr> shouldRead = getShouldRead(); 211 // a and a == null 212 assertEquals(2, Iterables.size(shouldRead)); 213 assertFalse(a.getShouldReadFlags().isEmpty()); 214 assertTrue(a.getShouldReadFlags().get(a.getId())); 215 assertTrue(b.getShouldReadFlags().isEmpty()); 216 assertTrue(c.getShouldReadFlags().isEmpty()); 217 assertTrue(d.getShouldReadFlags().isEmpty()); 218 assertTrue(e.getShouldReadFlags().isEmpty()); 219 220 221 Iterable<Expr> readFirst = getReadFirst(shouldRead, null); 222 assertEquals(1, Iterables.size(readFirst)); 223 final Expr first = Iterables.getFirst(readFirst, null); 224 assertSame(a, first); 225 assertTrue(mExprModel.markBitsRead()); 226 for (Expr expr : mExprModel.getPendingExpressions()) { 227 assertNull(expr.mShouldReadFlags); 228 } 229 shouldRead = getShouldRead(); 230 assertExactMatch(shouldRead, e, b, bIsNull); 231 232 assertFlags(e, aTernary.getRequirementFlagIndex(false)); 233 234 assertFlags(b, aTernary.getRequirementFlagIndex(true)); 235 assertFlags(bIsNull, aTernary.getRequirementFlagIndex(true)); 236 assertTrue(mExprModel.markBitsRead()); 237 shouldRead = getShouldRead(); 238 assertEquals(4, Iterables.size(shouldRead)); 239 assertTrue(Iterables.contains(shouldRead, c)); 240 assertTrue(Iterables.contains(shouldRead, d)); 241 assertTrue(Iterables.contains(shouldRead, aTernary)); 242 assertTrue(Iterables.contains(shouldRead, bTernary)); 243 244 assertTrue(c.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(true))); 245 assertEquals(1, c.getShouldReadFlags().cardinality()); 246 247 assertTrue(d.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(false))); 248 assertEquals(1, d.getShouldReadFlags().cardinality()); 249 250 assertTrue(bTernary.getShouldReadFlags().get(aTernary.getRequirementFlagIndex(true))); 251 assertEquals(1, bTernary.getShouldReadFlags().cardinality()); 252 253 assertEquals(5, aTernary.getShouldReadFlags().cardinality()); 254 for (Expr expr : new Expr[]{a, b, c, d, e}) { 255 assertTrue(aTernary.getShouldReadFlags().get(expr.getId())); 256 } 257 258 readFirst = getReadFirst(shouldRead); 259 assertEquals(2, Iterables.size(readFirst)); 260 assertTrue(Iterables.contains(readFirst, c)); 261 assertTrue(Iterables.contains(readFirst, d)); 262 assertFalse(mExprModel.markBitsRead()); 263 } 264 265 @Test 266 public void testPostConditionalDependencies() { 267 LayoutBinder lb = new MockLayoutBinder(); 268 mExprModel = lb.getModel(); 269 270 271 IdentifierExpr u1 = lb.addVariable("u1", User.class.getCanonicalName()); 272 IdentifierExpr u2 = lb.addVariable("u2", User.class.getCanonicalName()); 273 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); 274 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName()); 275 IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName()); 276 IdentifierExpr d = lb.addVariable("d", int.class.getCanonicalName()); 277 IdentifierExpr e = lb.addVariable("e", int.class.getCanonicalName()); 278 TernaryExpr abTernary = parse(lb, "a > b ? u1.name : u2.name", TernaryExpr.class); 279 TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx` + u2.getCond(e) ", TernaryExpr.class); 280 Expr abCmp = abTernary.getPred(); 281 Expr bcCmp = bcTernary.getPred(); 282 Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred(); 283 final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse(); 284 Expr u2GetCondE = xxPlusU2getCondE.getRight(); 285 Expr u1Name = abTernary.getIfTrue(); 286 Expr u2Name = abTernary.getIfFalse(); 287 Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue(); 288 Expr u2LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfFalse(); 289 290 291 mExprModel.seal(); 292 Iterable<Expr> shouldRead = getShouldRead(); 293 294 assertExactMatch(shouldRead, a, b, c, abCmp, bcCmp); 295 296 Iterable<Expr> firstRead = getReadFirst(shouldRead); 297 298 assertExactMatch(firstRead, a, b, c); 299 300 assertFlags(a, a, b, u1, u2, u1Name, u2Name); 301 assertFlags(b, a, b, u1, u2, u1Name, u2Name, c, d, u1LastName, u2LastName, e); 302 assertFlags(c, b, c, u1, d, u1LastName, u2LastName, e); 303 assertFlags(abCmp, a, b, u1, u2, u1Name, u2Name); 304 assertFlags(bcCmp, b, c, u1, d, u1LastName, u2LastName, e); 305 306 assertTrue(mExprModel.markBitsRead()); 307 308 shouldRead = getShouldRead(); 309 Expr[] batch = {d, e, u1, u2, u1GetCondD, u2GetCondE, xxPlusU2getCondE, abTernary, 310 abTernary.getIfTrue(), abTernary.getIfFalse()}; 311 assertExactMatch(shouldRead, batch); 312 firstRead = getReadFirst(shouldRead); 313 assertExactMatch(firstRead, d, e, u1, u2); 314 315 assertFlags(d, bcTernary.getRequirementFlagIndex(true)); 316 assertFlags(e, bcTernary.getRequirementFlagIndex(false)); 317 assertFlags(u1, bcTernary.getRequirementFlagIndex(true), abTernary.getRequirementFlagIndex(true)); 318 assertFlags(u2, bcTernary.getRequirementFlagIndex(false), abTernary.getRequirementFlagIndex(false)); 319 320 assertFlags(u1GetCondD, bcTernary.getRequirementFlagIndex(true)); 321 assertFlags(u2GetCondE, bcTernary.getRequirementFlagIndex(false)); 322 assertFlags(xxPlusU2getCondE, bcTernary.getRequirementFlagIndex(false)); 323 assertFlags(abTernary, a, b, u1, u2, u1Name, u2Name); 324 assertFlags(abTernary.getIfTrue(), abTernary.getRequirementFlagIndex(true)); 325 assertFlags(abTernary.getIfFalse(), abTernary.getRequirementFlagIndex(false)); 326 327 assertTrue(mExprModel.markBitsRead()); 328 329 shouldRead = getShouldRead(); 330 // actually, there is no real case to read u1 anymore because if b>c was not true, 331 // u1.getCond(d) will never be set. Right now, we don't have mechanism to figure this out 332 // and also it does not affect correctness (just an unnecessary if stmt) 333 assertExactMatch(shouldRead, u2, u1LastName, u2LastName, bcTernary.getIfTrue(), bcTernary); 334 firstRead = getReadFirst(shouldRead); 335 assertExactMatch(firstRead, u1LastName, u2); 336 337 assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true)); 338 assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false)); 339 assertFlags(u2, bcTernary.getIfTrue().getRequirementFlagIndex(false)); 340 341 assertFlags(bcTernary.getIfTrue(), bcTernary.getRequirementFlagIndex(true)); 342 assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e); 343 } 344 345 346 347 @Test 348 public void testCircularDependency() { 349 LayoutBinder lb = new MockLayoutBinder(); 350 mExprModel = lb.getModel(); 351 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); 352 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName()); 353 final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class); 354 mExprModel.seal(); 355 Iterable<Expr> shouldRead = getShouldRead(); 356 assertExactMatch(shouldRead, a, abTernary.getPred()); 357 assertTrue(mExprModel.markBitsRead()); 358 shouldRead = getShouldRead(); 359 assertExactMatch(shouldRead, b, abTernary); 360 assertFalse(mExprModel.markBitsRead()); 361 } 362 363 @Test 364 public void testNestedCircularDependency() { 365 LayoutBinder lb = new MockLayoutBinder(); 366 mExprModel = lb.getModel(); 367 IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); 368 IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName()); 369 IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName()); 370 final TernaryExpr a3Ternary = parse(lb, "a > 3 ? c > 4 ? a : b : c", TernaryExpr.class); 371 final TernaryExpr c4Ternary = (TernaryExpr) a3Ternary.getIfTrue(); 372 mExprModel.seal(); 373 Iterable<Expr> shouldRead = getShouldRead(); 374 assertExactMatch(shouldRead, a, a3Ternary.getPred()); 375 assertTrue(mExprModel.markBitsRead()); 376 shouldRead = getShouldRead(); 377 assertExactMatch(shouldRead, c, c4Ternary.getPred()); 378 assertFlags(c, a3Ternary.getRequirementFlagIndex(true), 379 a3Ternary.getRequirementFlagIndex(false)); 380 assertFlags(c4Ternary.getPred(), a3Ternary.getRequirementFlagIndex(true)); 381 } 382 383 @Test 384 public void testNoFlagsForNonBindingStatic() { 385 LayoutBinder lb = new MockLayoutBinder(); 386 mExprModel = lb.getModel(); 387 lb.addVariable("a", int.class.getCanonicalName()); 388 final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class); 389 mExprModel.seal(); 390 assertTrue(parsed.getRight().getInvalidFlags().isEmpty()); 391 assertEquals(1, parsed.getLeft().getInvalidFlags().cardinality()); 392 assertEquals(1, mExprModel.getInvalidateableFieldLimit()); 393 } 394 395 @Test 396 public void testFlagsForBindingStatic() { 397 LayoutBinder lb = new MockLayoutBinder(); 398 mExprModel = lb.getModel(); 399 lb.addVariable("a", int.class.getCanonicalName()); 400 final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class); 401 final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class); 402 mExprModel.seal(); 403 assertTrue(staticParsed.isBindingExpression()); 404 assertEquals(1, staticParsed.getInvalidFlags().cardinality()); 405 assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags()); 406 assertEquals(1, parsed.getLeft().getInvalidFlags().cardinality()); 407 assertEquals(2, mExprModel.getInvalidateableFieldLimit()); 408 } 409 410 @Test 411 public void testPartialNeededRead() { 412 throw new NotImplementedException("create a test that has a variable which can be read for " 413 + "some flags and also may be read for some condition. Try both must match and" 414 + " partial match and none-match in conditionals"); 415 } 416 417 private void assertFlags(Expr a, int... flags) { 418 BitSet bitset = new BitSet(); 419 for (int flag : flags) { 420 bitset.set(flag); 421 } 422 assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags()); 423 } 424 425 private void assertFlags(Expr a, Expr... exprs) { 426 BitSet bitSet = a.getShouldReadFlags(); 427 for (Expr expr : exprs) { 428 BitSet clone = (BitSet) bitSet.clone(); 429 clone.and(expr.getInvalidFlags()); 430 assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr 431 .getUniqueKey(), expr.getInvalidFlags(), clone); 432 } 433 434 BitSet composite = new BitSet(); 435 for (Expr expr : exprs) { 436 composite.or(expr.getInvalidFlags()); 437 } 438 assertEquals("composite flags should match", composite, bitSet); 439 } 440 441 private void assertExactMatch(Iterable<Expr> iterable, Expr... exprs) { 442 int i = 0; 443 log("list", iterable); 444 for (Expr expr : exprs) { 445 assertTrue((i++) + ":must contain " + expr.getUniqueKey(), Iterables.contains(iterable, expr)); 446 } 447 i = 0; 448 for (Expr expr : iterable) { 449 assertTrue((i++) + ":must be expected " + expr.getUniqueKey(), ArrayUtils.contains(exprs, expr)); 450 } 451 } 452 453 private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) { 454 final Expr parsed = binder.parse(input); 455 assertTrue(klass.isAssignableFrom(parsed.getClass())); 456 return (T) parsed; 457 } 458 459 private void log(String s, Iterable<Expr> iterable) { 460 L.d(s); 461 for (Expr e : iterable) { 462 L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(), e.getShouldReadFlagsWithConditionals(), e.getReadSoFar()); 463 } 464 L.d("end of %s", s); 465 } 466 467// private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead) { 468// return mExprModel.filterCanBeReadNow(shouldRead); 469// } 470 471 private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead) { 472 return getReadFirst(shouldRead, null); 473 } 474 private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead, final Iterable<Expr> justRead) { 475 return Iterables.filter(shouldRead, new Predicate<Expr>() { 476 @Override 477 public boolean apply(Expr input) { 478 return input.shouldReadNow(justRead); 479 } 480 }); 481 } 482 483 private Iterable<Expr> getShouldRead() { 484 return mExprModel.filterShouldRead(mExprModel.getPendingExpressions()); 485 } 486 487 public static class User { 488 String name; 489 String lastName; 490 491 public String getName() { 492 return name; 493 } 494 495 public String getLastName() { 496 return lastName; 497 } 498 499 public boolean getCond(int i) { 500 return true; 501 } 502 } 503} 504