aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/com/google/escapevelocity/MethodFinderTest.java92
-rw-r--r--src/test/java/com/google/escapevelocity/ReferenceNodeTest.java18
-rw-r--r--src/test/java/com/google/escapevelocity/TemplateTest.java169
3 files changed, 232 insertions, 47 deletions
diff --git a/src/test/java/com/google/escapevelocity/MethodFinderTest.java b/src/test/java/com/google/escapevelocity/MethodFinderTest.java
new file mode 100644
index 0000000..66b8948
--- /dev/null
+++ b/src/test/java/com/google/escapevelocity/MethodFinderTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 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.escapevelocity;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toSet;
+
+import com.google.common.collect.ImmutableMap;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MethodFinderTest {
+ @Test
+ public void visibleMethodFromClass() throws Exception {
+ Map<String, String> map = Collections.singletonMap("foo", "bar");
+ Class<?> mapClass = map.getClass();
+ assertThat(Modifier.isPublic(mapClass.getModifiers())).isFalse();
+
+ Method size = mapClass.getMethod("size");
+ Method visibleSize = MethodFinder.visibleMethod(size, mapClass);
+ assertThat(visibleSize.getDeclaringClass().isInterface()).isFalse();
+ assertThat(visibleSize.invoke(map)).isEqualTo(1);
+ }
+
+ @Test
+ public void visibleMethodFromInterface() throws Exception {
+ Map<String, String> map = ImmutableMap.of("foo", "bar");
+ Map.Entry<String, String> entry = map.entrySet().iterator().next();
+ Class<?> entryClass = entry.getClass();
+ assertThat(Modifier.isPublic(entryClass.getModifiers())).isFalse();
+
+ Method getValue = entryClass.getMethod("getValue");
+ Method visibleGetValue = MethodFinder.visibleMethod(getValue, entryClass);
+ assertThat(visibleGetValue.getDeclaringClass().isInterface()).isTrue();
+ assertThat(visibleGetValue.invoke(entry)).isEqualTo("bar");
+ }
+
+ @Test
+ public void publicMethodsWithName() {
+ List<String> list = Collections.singletonList("foo");
+ Class<?> listClass = list.getClass();
+ assertThat(Modifier.isPublic(listClass.getModifiers())).isFalse();
+
+ MethodFinder methodFinder = new MethodFinder();
+ Set<Method> methods = methodFinder.publicMethodsWithName(listClass, "remove");
+ // This should find at least remove(int) and remove(Object).
+ assertThat(methods.size()).isAtLeast(2);
+ assertThat(methods.stream().map(Method::getName).collect(toSet())).containsExactly("remove");
+ assertThat(methods.stream().allMatch(MethodFinderTest::isPublic)).isTrue();
+
+ // We should cache the result, meaning we get back the same result if we ask a second time.
+ Set<Method> methods2 = methodFinder.publicMethodsWithName(listClass, "remove");
+ assertThat(methods2).isSameInstanceAs(methods);
+ }
+
+ @Test
+ public void publicMethodsWithName_Nonexistent() {
+ List<String> list = Collections.singletonList("foo");
+ Class<?> listClass = list.getClass();
+ assertThat(Modifier.isPublic(listClass.getModifiers())).isFalse();
+
+ MethodFinder methodFinder = new MethodFinder();
+ Set<Method> methods = methodFinder.publicMethodsWithName(listClass, "nonexistentMethod");
+ assertThat(methods).isEmpty();
+ }
+
+ private static boolean isPublic(Method method) {
+ return Modifier.isPublic(method.getModifiers())
+ && Modifier.isPublic(method.getDeclaringClass().getModifiers());
+ }
+}
diff --git a/src/test/java/com/google/escapevelocity/ReferenceNodeTest.java b/src/test/java/com/google/escapevelocity/ReferenceNodeTest.java
index 3e784f6..b1759bd 100644
--- a/src/test/java/com/google/escapevelocity/ReferenceNodeTest.java
+++ b/src/test/java/com/google/escapevelocity/ReferenceNodeTest.java
@@ -17,15 +17,11 @@ package com.google.escapevelocity;
import static com.google.common.truth.Truth.assertThat;
-import com.google.escapevelocity.ReferenceNode.MethodReferenceNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Primitives;
import com.google.common.truth.Expect;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Collections;
-import java.util.Map;
+import com.google.escapevelocity.ReferenceNode.MethodReferenceNode;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -85,22 +81,12 @@ public class ReferenceNodeTest {
MethodReferenceNode.primitiveTypeIsAssignmentCompatible(to, from);
expect
.withMessage(from + " assignable to " + to)
- .that(expected).isEqualTo(actual);
+ .that(actual).isEqualTo(expected);
}
}
}
@Test
- public void testVisibleMethod() throws Exception {
- Map<String, String> map = Collections.singletonMap("foo", "bar");
- Class<?> mapClass = map.getClass();
- assertThat(Modifier.isPublic(mapClass.getModifiers())).isFalse();
- Method size = map.getClass().getMethod("size");
- Method visibleSize = ReferenceNode.visibleMethod(size, mapClass);
- assertThat(visibleSize.invoke(map)).isEqualTo(1);
- }
-
- @Test
public void testCompatibleArgs() {
assertThat(MethodReferenceNode.compatibleArgs(
new Class<?>[]{int.class}, ImmutableList.of((Object) 5))).isTrue();
diff --git a/src/test/java/com/google/escapevelocity/TemplateTest.java b/src/test/java/com/google/escapevelocity/TemplateTest.java
index 04bad8e..0503125 100644
--- a/src/test/java/com/google/escapevelocity/TemplateTest.java
+++ b/src/test/java/com/google/escapevelocity/TemplateTest.java
@@ -16,10 +16,12 @@
package com.google.escapevelocity;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.truth.Expect;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
@@ -27,6 +29,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
+import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
@@ -36,6 +39,7 @@ import java.util.function.Supplier;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeInstance;
import org.apache.velocity.runtime.log.NullLogChute;
@@ -45,7 +49,6 @@ import org.apache.velocity.runtime.resource.loader.ResourceLoader;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -57,7 +60,6 @@ import org.junit.runners.JUnit4;
public class TemplateTest {
@Rule public TestName testName = new TestName();
@Rule public Expect expect = Expect.create();
- @Rule public ExpectedException thrown = ExpectedException.none();
private RuntimeInstance velocityRuntimeInstance;
@@ -128,6 +130,31 @@ public class TemplateTest {
return writer.toString();
}
+ private void expectParseException(
+ String template,
+ String expectedMessageSubstring) {
+ Exception velocityException = null;
+ try {
+ SimpleNode parsedTemplate =
+ velocityRuntimeInstance.parse(new StringReader(template), testName.getMethodName());
+ VelocityContext velocityContext = new VelocityContext(new TreeMap<>());
+ velocityRuntimeInstance.render(
+ velocityContext, new StringWriter(), parsedTemplate.getTemplateName(), parsedTemplate);
+ fail("Velocity did not throw an exception for this template");
+ } catch (org.apache.velocity.runtime.parser.ParseException | VelocityException expected) {
+ velocityException = expected;
+ }
+ try {
+ Template.parseFrom(new StringReader(template));
+ fail("Velocity generated an exception, but EscapeVelocity did not: " + velocityException);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ } catch (ParseException expected) {
+ assertWithMessage("Got expected exception, but message did not match")
+ .that(expected).hasMessageThat().contains(expectedMessageSubstring);
+ }
+ }
+
@Test
public void empty() {
compare("");
@@ -210,13 +237,6 @@ public class TemplateTest {
compare("$foo.!", ImmutableMap.of("foo", false));
}
- /* TODO(emcmanus): make this work.
- @Test
- public void substituteNotPropertyId() {
- compare("$foo.!", ImmutableMap.of("foo", false));
- }
- */
-
@Test
public void substituteNestedProperty() {
compare("\n$t.name.empty\n", ImmutableMap.of("t", Thread.currentThread()));
@@ -228,23 +248,64 @@ public class TemplateTest {
}
@Test
+ public void substituteMethodNoArgsSyntheticOverride() {
+ compare("<$c.isEmpty()>", ImmutableMap.of("c", ImmutableSetMultimap.of()));
+ }
+
+ @Test
public void substituteMethodOneArg() {
compare("<$list.get(0)>", ImmutableMap.of("list", ImmutableList.of("foo")));
}
@Test
+ public void substituteMethodOneNullArg() {
+ // This should evaluate map.containsKey(map.get("absent")), which is map.containsKey(null).
+ compare("<$map.containsKey($map.get(\"absent\"))>", ImmutableMap.of("map", ImmutableMap.of()));
+ }
+
+ @Test
public void substituteMethodTwoArgs() {
compare("\n$s.indexOf(\"bar\", 2)\n", ImmutableMap.of("s", "barbarbar"));
}
@Test
- public void substituteMethodNoSynthetic() {
+ public void substituteMethodSyntheticOverloads() {
// If we aren't careful, we'll see both the inherited `Set<K> keySet()` from Map
// and the overridden `ImmutableSet<K> keySet()` in ImmutableMap.
compare("$map.keySet()", ImmutableMap.of("map", ImmutableMap.of("foo", "bar")));
}
@Test
+ public void substituteStaticMethod() {
+ compare("$Integer.toHexString(23)", ImmutableMap.of("Integer", Integer.class));
+ }
+
+ @Test
+ public void substituteStaticMethodAsInstanceMethod() {
+ compare("$i.toHexString(23)", ImmutableMap.of("i", 0));
+ }
+
+ @Test
+ public void substituteClassMethod() {
+ // This is Class.getName().
+ compare("$Integer.getName()", ImmutableMap.of("Integer", Integer.class));
+ }
+
+ /** See {@link #substituteClassOrInstanceMethod}. */
+ public static class GetName {
+ public static String getName() {
+ return "Noddy";
+ }
+ }
+
+ @Test
+ public void substituteClassOrInstanceMethod() {
+ // If the method exists as both an instance method on Class and a static method on the named
+ // class, it's the instance method that wins, so this is still Class.getName().
+ compare("$GetName.getName()", ImmutableMap.of("GetName", GetName.class));
+ }
+
+ @Test
public void substituteIndexNoBraces() {
compare("<$map[\"x\"]>", ImmutableMap.of("map", ImmutableMap.of("x", "y")));
}
@@ -254,6 +315,14 @@ public class TemplateTest {
compare("<${map[\"x\"]}>", ImmutableMap.of("map", ImmutableMap.of("x", "y")));
}
+ // Velocity allows you to write $map.foo instead of $map["foo"].
+ @Test
+ public void substituteMapProperty() {
+ compare("$map.foo", ImmutableMap.of("map", ImmutableMap.of("foo", "bar")));
+ // $map.empty is always equivalent to $map["empty"], never Map.isEmpty().
+ compare("$map.empty", ImmutableMap.of("map", ImmutableMap.of("empty", "foo")));
+ }
+
@Test
public void substituteIndexThenProperty() {
compare("<$map[2].name>", ImmutableMap.of("map", ImmutableMap.of(2, getClass())));
@@ -291,6 +360,29 @@ public class TemplateTest {
}
@Test
+ public void substituteInString() {
+ String template =
+ "#foreach ($a in $list)"
+ + "#set ($s = \"THING_${foreach.index}\")"
+ + "$s,$s;"
+ + "#end";
+ compare(template, ImmutableMap.of("list", ImmutableList.of(1, 2, 3)));
+ compare("#set ($s = \"$x\") <$s>", ImmutableMap.of("x", "fred"));
+ compare("#set ($s = \"==$x$y\") <$s>", ImmutableMap.of("x", "fred", "y", "jim"));
+ compare("#set ($s = \"$x$y==\") <$s>", ImmutableMap.of("x", "fred", "y", "jim"));
+ }
+
+ @Test
+ public void stringOperationsOnSubstitution() {
+ compare("#set ($s = \"a${b}c\") $s.length()", ImmutableMap.of("b", 23));
+ }
+
+ @Test
+ public void singleQuoteNoSubstitution() {
+ compare("#set ($s = 'a${b}c') x${s}y", ImmutableMap.of("b", 23));
+ }
+
+ @Test
public void simpleSet() {
compare("$x#set ($x = 17)#set ($y = 23) ($x, $y)", ImmutableMap.of("x", 1));
}
@@ -506,6 +598,18 @@ public class TemplateTest {
}
@Test
+ public void forEachIndex() {
+ String template =
+ "#foreach ($x in $list)"
+ + "[$foreach.index]"
+ + "#foreach ($y in $list)"
+ + "($foreach.index)==$x.$y=="
+ + "#end"
+ + "#end";
+ compare(template, ImmutableMap.of("list", ImmutableList.of("blim", "blam", "blum")));
+ }
+
+ @Test
public void setSpacing() {
// The spacing in the output from #set is eccentric.
compare("x#set ($x = 0)x");
@@ -550,6 +654,18 @@ public class TemplateTest {
compare(template, ImmutableMap.of("x", "tiddly"));
}
+ @Test
+ public void macroWithCommaSeparatedArgs() {
+ String template =
+ "$x\n"
+ + "#macro (m, $x, $y)\n"
+ + " #if ($x < $y) less #else greater #end\n"
+ + "#end\n"
+ + "#m(17 23) #m(23 17) #m(17 17)\n"
+ + "$x";
+ compare(template, ImmutableMap.of("x", "tiddly"));
+ }
+
/**
* Tests defining a macro inside a conditional. This proves that macros are not evaluated in the
* main control flow, but rather are extracted at parse time. It also tests what happens if there
@@ -706,45 +822,36 @@ public class TemplateTest {
}
@Test
- public void badBraceReference() throws IOException {
+ public void badBraceReference() {
String template = "line 1\nline 2\nbar${foo.!}baz";
- thrown.expect(ParseException.class);
- thrown.expectMessage("Expected }, on line 3, at text starting: .!}baz");
- Template.parseFrom(new StringReader(template));
+ expectParseException(template, "Expected }, on line 3, at text starting: .!}baz");
}
@Test
- public void undefinedMacro() throws IOException {
+ public void undefinedMacro() {
String template = "#oops()";
- thrown.expect(ParseException.class);
- thrown.expectMessage("#oops is neither a standard directive nor a macro that has been defined");
- Template.parseFrom(new StringReader(template));
+ expectParseException(
+ template,
+ "#oops is neither a standard directive nor a macro that has been defined");
}
@Test
- public void macroArgumentMismatch() throws IOException {
+ public void macroArgumentMismatch() {
String template =
"#macro (twoArgs $a $b) $a $b #end\n"
+ "#twoArgs(23)\n";
- thrown.expect(ParseException.class);
- thrown.expectMessage("Wrong number of arguments to #twoArgs: expected 2, got 1");
- Template.parseFrom(new StringReader(template));
+ expectParseException(template, "Wrong number of arguments to #twoArgs: expected 2, got 1");
}
@Test
- public void unclosedBlockQuote() throws IOException {
+ public void unclosedBlockQuote() {
String template = "foo\nbar #[[\nblah\nblah";
- thrown.expect(ParseException.class);
- thrown.expectMessage("Unterminated #[[ - did not see matching ]]#, on line 2");
- Template.parseFrom(new StringReader(template));
+ expectParseException(template, "Unterminated #[[ - did not see matching ]]#, on line 2");
}
@Test
- public void unclosedBlockComment() throws IOException {
- String template = "foo\nbar #*\nblah\nblah";
- thrown.expect(ParseException.class);
- thrown.expectMessage("Unterminated #* - did not see matching *#, on line 2");
- Template.parseFrom(new StringReader(template));
+ public void unclosedBlockComment() {
+ compare("foo\nbar #*\nblah\nblah");
}
/**