/* * Copyright 2013 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.template.soy.passes; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.template.soy.SoyFileSetParser.ParseResult; import com.google.template.soy.SoyFileSetParserBuilder; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.ExplodingErrorReporter; import com.google.template.soy.passes.FindTransitiveDepTemplatesVisitor.TransitiveDepTemplatesInfo; import com.google.template.soy.soytree.SoyFileSetNode; import com.google.template.soy.soytree.TemplateNode; import com.google.template.soy.soytree.TemplateRegistry; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Unit tests for FindTransitiveDepTemplatesVisitor. * */ @RunWith(JUnit4.class) public final class FindTransitiveDepTemplatesVisitorTest { private static final ErrorReporter FAIL = ExplodingErrorReporter.get(); @Test public void testSimple() { // aaa -> {bbb, ccc}, bbb -> ddd. String fileContent = "" + "{namespace ns autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/***/\n" + "{template .aaa}\n" + " {call .bbb /} {$ij.boo} {call .ccc /} {$ij.foo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .bbb}\n" + " {$ij.boo} {$ij.goo} {call .ddd /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ccc}\n" + " {$ij.boo} {$ij.moo + $ij.woo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ddd}\n" + " {$ij.boo} {$ij.moo} {round($ij.zoo)}\n" + "{/template}\n"; ParseResult result = SoyFileSetParserBuilder.forFileContents(fileContent).errorReporter(FAIL).parse(); TemplateRegistry templateRegistry = result.registry(); SoyFileSetNode soyTree = result.fileSet(); TemplateNode aaa = soyTree.getChild(0).getChild(0); TemplateNode bbb = soyTree.getChild(0).getChild(1); TemplateNode ccc = soyTree.getChild(0).getChild(2); TemplateNode ddd = soyTree.getChild(0).getChild(3); // Test with exec(aaa). // Exercises: processCalleeHelper case 5 with incorporateCalleeVisitInfo case 1 (aaa -> bbb). FindTransitiveDepTemplatesVisitor visitor = new FindTransitiveDepTemplatesVisitor(templateRegistry); Map<TemplateNode, TransitiveDepTemplatesInfo> memoizedInfoMap = visitor.templateToFinishedInfoMap; visitor.exec(aaa); assertThat(memoizedInfoMap).hasSize(4); assertThat(memoizedInfoMap.get(ddd).depTemplateSet).isEqualTo(ImmutableSet.of(ddd)); assertThat(memoizedInfoMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc)); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, ddd)); assertThat(memoizedInfoMap.get(aaa).depTemplateSet) .isEqualTo(ImmutableSet.of(aaa, bbb, ccc, ddd)); // Test with exec(bbb) then exec(aaa). // Exercises: processCalleeHelper case 1 (aaa -> bbb). visitor = new FindTransitiveDepTemplatesVisitor(templateRegistry); memoizedInfoMap = visitor.templateToFinishedInfoMap; visitor.exec(bbb); assertThat(memoizedInfoMap).hasSize(2); assertThat(memoizedInfoMap.get(ddd).depTemplateSet).isEqualTo(ImmutableSet.of(ddd)); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, ddd)); visitor.exec(aaa); assertThat(memoizedInfoMap).hasSize(4); assertThat(memoizedInfoMap.get(ddd).depTemplateSet).isEqualTo(ImmutableSet.of(ddd)); assertThat(memoizedInfoMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc)); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, ddd)); assertThat(memoizedInfoMap.get(aaa).depTemplateSet) .isEqualTo(ImmutableSet.of(aaa, bbb, ccc, ddd)); } @Test public void testTwoPathsToSameTemplate() { // aaa -> {bbb, ccc}, ccc -> bbb. String fileContent = "" + "{namespace ns autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/***/\n" + "{template .aaa}\n" + " {call .bbb /} {$ij.boo} {call .ccc /} {$ij.foo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .bbb}\n" + " {$ij.boo} {$ij.goo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ccc}\n" + " {$ij.boo} {$ij.moo + $ij.woo} {call .bbb /}\n" + "{/template}\n"; ParseResult result = SoyFileSetParserBuilder.forFileContents(fileContent).errorReporter(FAIL).parse(); TemplateRegistry templateRegistry = result.registry(); SoyFileSetNode soyTree = result.fileSet(); TemplateNode aaa = soyTree.getChild(0).getChild(0); TemplateNode bbb = soyTree.getChild(0).getChild(1); TemplateNode ccc = soyTree.getChild(0).getChild(2); // Test with exec(aaa). // Exercises: processCalleeHelper case 4 with incorporateCalleeVisitInfo case 1 (ccc -> bbb). FindTransitiveDepTemplatesVisitor visitor = new FindTransitiveDepTemplatesVisitor(templateRegistry); Map<TemplateNode, TransitiveDepTemplatesInfo> memoizedInfoMap = visitor.templateToFinishedInfoMap; visitor.exec(aaa); assertThat(memoizedInfoMap).hasSize(3); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb)); assertThat(memoizedInfoMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc, bbb)); assertThat(memoizedInfoMap.get(aaa).depTemplateSet).isEqualTo(ImmutableSet.of(aaa, bbb, ccc)); } @Test public void testSimpleRecursion() { // Tests direct recursion (cycle of 1) and indirect recursion with a cycle of 2. // aaa -> bbb, bbb -> {bbb, ccc}, ccc -> bbb. String fileContent = "" + "{namespace ns autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/***/\n" + "{template .aaa}\n" + " {call .bbb /} {$ij.boo} {$ij.foo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .bbb}\n" + " {$ij.boo} {$ij.goo} {call .bbb /} {call .ccc /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ccc}\n" + " {$ij.boo} {call .bbb /} {$ij.moo}\n" + "{/template}\n"; ParseResult result = SoyFileSetParserBuilder.forFileContents(fileContent).errorReporter(FAIL).parse(); TemplateRegistry templateRegistry = result.registry(); SoyFileSetNode soyTree = result.fileSet(); TemplateNode aaa = soyTree.getChild(0).getChild(0); TemplateNode bbb = soyTree.getChild(0).getChild(1); TemplateNode ccc = soyTree.getChild(0).getChild(2); // Test with exec(aaa). // Exercises: processCalleeHelper case 2 (bbb -> bbb). // Exercises: processCalleeHelper case 3 (ccc -> bbb). // Exercises: processCalleeHelper case 5 with incorporateCalleeVisitInfo case 2 (bbb -> ccc). FindTransitiveDepTemplatesVisitor visitor = new FindTransitiveDepTemplatesVisitor(templateRegistry); Map<TemplateNode, TransitiveDepTemplatesInfo> memoizedInfoMap = visitor.templateToFinishedInfoMap; visitor.exec(aaa); assertThat(memoizedInfoMap).hasSize(3); assertThat(memoizedInfoMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc, bbb)); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, ccc)); assertThat(memoizedInfoMap.get(aaa).depTemplateSet).isEqualTo(ImmutableSet.of(aaa, bbb, ccc)); } @Test public void testLargerRecursiveCycle() { // Tests indirect recursion with a cycle of 3. // aaa -> bbb, bbb -> ccc, ccc -> aaa. String fileContent = "" + "{namespace ns autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/***/\n" + "{template .aaa}\n" + " {$ij.foo} {$ij.boo} {call .bbb /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .bbb}\n" + " {$ij.goo} {call .ccc /} {$ij.boo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ccc}\n" + " {call .aaa /} {$ij.moo} {$ij.boo}\n" + "{/template}\n"; ParseResult result = SoyFileSetParserBuilder.forFileContents(fileContent).errorReporter(FAIL).parse(); TemplateRegistry templateRegistry = result.registry(); SoyFileSetNode soyTree = result.fileSet(); TemplateNode aaa = soyTree.getChild(0).getChild(0); TemplateNode bbb = soyTree.getChild(0).getChild(1); TemplateNode ccc = soyTree.getChild(0).getChild(2); // Test with exec(aaa). // Exercises: processCalleeHelper case 3 (ccc-> aaa). // Exercises: processCalleeHelper case 5 with incorporateCalleeVisitInfo case 3 (bbb -> ccc). // Exercises: processCalleeHelper case 5 with incorporateCalleeVisitInfo case 2 (aaa -> bbb). FindTransitiveDepTemplatesVisitor visitor = new FindTransitiveDepTemplatesVisitor(templateRegistry); Map<TemplateNode, TransitiveDepTemplatesInfo> memoizedInfoMap = visitor.templateToFinishedInfoMap; visitor.exec(aaa); assertThat(memoizedInfoMap).hasSize(3); assertThat(memoizedInfoMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc, aaa, bbb)); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, ccc, aaa)); assertThat(memoizedInfoMap.get(aaa).depTemplateSet).isEqualTo(ImmutableSet.of(aaa, bbb, ccc)); } @Test public void testTwoPathsToSameRecursiveCycle() { // aaa -> {bbb, ccc}, bbb -> ddd, ccc -> ddd, ddd -> bbb. String fileContent = "" + "{namespace ns autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/***/\n" + "{template .aaa}\n" + " {$ij.boo} {$ij.foo} {call .bbb /} {call .ccc /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .bbb}\n" + " {$ij.boo} {$ij.goo} {call .ddd /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ccc}\n" + " {$ij.boo} {$ij.moo} {call .ddd /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ddd}\n" + " {$ij.boo} {$ij.too} {call .bbb /}\n" + "{/template}\n"; ParseResult result = SoyFileSetParserBuilder.forFileContents(fileContent).errorReporter(FAIL).parse(); TemplateRegistry templateRegistry = result.registry(); SoyFileSetNode soyTree = result.fileSet(); TemplateNode aaa = soyTree.getChild(0).getChild(0); TemplateNode bbb = soyTree.getChild(0).getChild(1); TemplateNode ccc = soyTree.getChild(0).getChild(2); TemplateNode ddd = soyTree.getChild(0).getChild(3); // Test with exec(aaa). // Exercises: processCalleeHelper case 4 with incorporateCalleeVisitInfo case 4 (ccc -> ddd). FindTransitiveDepTemplatesVisitor visitor = new FindTransitiveDepTemplatesVisitor(templateRegistry); Map<TemplateNode, TransitiveDepTemplatesInfo> memoizedInfoMap = visitor.templateToFinishedInfoMap; visitor.exec(aaa); assertThat(memoizedInfoMap).hasSize(4); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, ddd)); assertThat(memoizedInfoMap.get(ddd).depTemplateSet).isEqualTo(ImmutableSet.of(ddd, bbb)); assertThat(memoizedInfoMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc, ddd, bbb)); assertThat(memoizedInfoMap.get(aaa).depTemplateSet) .isEqualTo(ImmutableSet.of(aaa, bbb, ccc, ddd)); } @Test public void testSmallerRecursiveCycleInLargerRecursiveCycle() { // aaa -> {bbb, ccc}, bbb -> aaa, ccc -> bbb. String fileContent = "" + "{namespace ns autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/***/\n" + "{template .aaa}\n" + " {$ij.foo} {$ij.boo} {call .bbb /} {call .ccc /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .bbb}\n" + " {$ij.goo} {$ij.boo} {call .aaa /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ccc}\n" + " {$ij.moo} {$ij.boo} {call .bbb /}\n" + "{/template}\n"; ParseResult result = SoyFileSetParserBuilder.forFileContents(fileContent).errorReporter(FAIL).parse(); TemplateRegistry templateRegistry = result.registry(); SoyFileSetNode soyTree = result.fileSet(); TemplateNode aaa = soyTree.getChild(0).getChild(0); TemplateNode bbb = soyTree.getChild(0).getChild(1); TemplateNode ccc = soyTree.getChild(0).getChild(2); // Test with exec(aaa). // Exercises: processCalleeHelper case 4 with incorporateCalleeVisitInfo case 3 (ccc -> bbb). FindTransitiveDepTemplatesVisitor visitor = new FindTransitiveDepTemplatesVisitor(templateRegistry); Map<TemplateNode, TransitiveDepTemplatesInfo> memoizedInfoMap = visitor.templateToFinishedInfoMap; visitor.exec(aaa); assertThat(memoizedInfoMap).hasSize(3); assertThat(memoizedInfoMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc, bbb, aaa)); assertThat(memoizedInfoMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, aaa, ccc)); assertThat(memoizedInfoMap.get(aaa).depTemplateSet).isEqualTo(ImmutableSet.of(aaa, bbb, ccc)); } @Test public void testExecOnAllTemplates() { // aaa -> {bbb, ccc}, bbb -> ddd. String fileContent = "" + "{namespace ns autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/***/\n" + "{template .bbb}\n" + " {$ij.boo} {$ij.goo} {call .ddd /}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .aaa}\n" + " {call .bbb /} {$ij.boo} {call .ccc /} {$ij.foo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ccc}\n" + " {$ij.boo} {$ij.moo + $ij.woo}\n" + "{/template}\n" + "\n" + "/***/\n" + "{template .ddd}\n" + " {$ij.boo} {$ij.moo} {round($ij.zoo)}\n" + "{/template}\n"; ParseResult result = SoyFileSetParserBuilder.forFileContents(fileContent).errorReporter(FAIL).parse(); TemplateRegistry templateRegistry = result.registry(); SoyFileSetNode soyTree = result.fileSet(); TemplateNode bbb = soyTree.getChild(0).getChild(0); TemplateNode aaa = soyTree.getChild(0).getChild(1); TemplateNode ccc = soyTree.getChild(0).getChild(2); TemplateNode ddd = soyTree.getChild(0).getChild(3); ImmutableMap<TemplateNode, TransitiveDepTemplatesInfo> resultMap = new FindTransitiveDepTemplatesVisitor(templateRegistry).execOnAllTemplates(soyTree); assertThat(resultMap).hasSize(4); assertThat(resultMap.get(ddd).depTemplateSet).isEqualTo(ImmutableSet.of(ddd)); assertThat(resultMap.get(ccc).depTemplateSet).isEqualTo(ImmutableSet.of(ccc)); assertThat(resultMap.get(bbb).depTemplateSet).isEqualTo(ImmutableSet.of(bbb, ddd)); assertThat(resultMap.get(aaa).depTemplateSet).isEqualTo(ImmutableSet.of(aaa, bbb, ccc, ddd)); } }