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