package org.elasticsearch.painless; import java.util.Collections; import static java.util.Collections.singletonMap; /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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. */ public class BasicExpressionTests extends ScriptTestCase { /** simple tests returning a constant value */ public void testReturnConstant() { assertEquals(5, exec("return 5")); assertEquals(6L, exec("return 6l")); assertEquals(7L, exec("return 7L")); assertEquals(7.0d, exec("return 7.0")); assertEquals(18.0d, exec("return 18d")); assertEquals(19.0d, exec("return 19.0d")); assertEquals(20.0d, exec("return 20D")); assertEquals(21.0d, exec("return 21.0D")); assertEquals(32.0F, exec("return 32.0f")); assertEquals(33.0F, exec("return 33f")); assertEquals(34.0F, exec("return 34.0F")); assertEquals(35.0F, exec("return 35F")); assertEquals((byte)255, exec("return (byte)255")); assertEquals((short)5, exec("return (short)5")); assertEquals("string", exec("return \"string\"")); assertEquals("string", exec("return 'string'")); assertEquals(true, exec("return true")); assertEquals(false, exec("return false")); assertNull(exec("return null")); } public void testReturnConstantChar() { assertEquals('x', exec("return (char)'x';")); } public void testConstantCharTruncation() { assertEquals('čš ', exec("return (char)100000;")); } public void testStringEscapes() { // The readability of this test suffers from having to escape `\` and `"` in java strings. Please be careful. Sorry! // `\\` is a `\` assertEquals("\\string", exec("\"\\\\string\"")); assertEquals("\\string", exec("'\\\\string'")); // `\"` is a `"` if surrounded by `"`s assertEquals("\"string", exec("\"\\\"string\"")); Exception e = expectScriptThrows(IllegalArgumentException.class, () -> exec("'\\\"string'", false)); assertEquals("unexpected character ['\\\"]. The only valid escape sequences in strings starting with ['] are [\\\\] and [\\'].", e.getMessage()); // `\'` is a `'` if surrounded by `'`s e = expectScriptThrows(IllegalArgumentException.class, () -> exec("\"\\'string\"", false)); assertEquals("unexpected character [\"\\']. The only valid escape sequences in strings starting with [\"] are [\\\\] and [\\\"].", e.getMessage()); assertEquals("'string", exec("'\\'string'")); // We don't break native escapes like new line assertEquals("\nstring", exec("\"\nstring\"")); assertEquals("\nstring", exec("'\nstring'")); // And we're ok with strings with multiple escape sequences assertEquals("\\str\"in\\g", exec("\"\\\\str\\\"in\\\\g\"")); assertEquals("st\\r'i\\ng", exec("'st\\\\r\\'i\\\\ng'")); } public void testStringTermination() { // `'` inside of a string delimited with `"` should be ok assertEquals("test'", exec("\"test'\"")); // `"` inside of a string delimited with `'` should be ok assertEquals("test\"", exec("'test\"'")); } /** declaring variables for primitive types */ public void testDeclareVariable() { assertEquals(5, exec("int i = 5; return i;")); assertEquals(7L, exec("long l = 7; return l;")); assertEquals(7.0, exec("double d = 7; return d;")); assertEquals(32.0F, exec("float f = 32F; return f;")); assertEquals((byte)255, exec("byte b = (byte)255; return b;")); assertEquals((short)5, exec("short s = (short)5; return s;")); assertEquals("string", exec("String s = \"string\"; return s;")); assertEquals(true, exec("boolean v = true; return v;")); assertEquals(false, exec("boolean v = false; return v;")); } public void testCast() { assertEquals(1, exec("return (int)1.0;")); assertEquals((byte)100, exec("double x = 100; return (byte)x;")); assertEquals(3, exec( "Map x = new HashMap();\n" + "Object y = x;\n" + "((Map)y).put(2, 3);\n" + "return x.get(2);\n")); } public void testIllegalDefCast() { Exception exception = expectScriptThrows(ClassCastException.class, () -> { exec("def x = 1.0; int y = x; return y;"); }); assertTrue(exception.getMessage().contains("cannot be cast")); exception = expectScriptThrows(ClassCastException.class, () -> { exec("def x = (short)1; byte y = x; return y;"); }); assertTrue(exception.getMessage().contains("cannot be cast")); } public void testCat() { assertEquals("aaabbb", exec("return \"aaa\" + \"bbb\";")); assertEquals("aaabbb", exec("String aaa = \"aaa\", bbb = \"bbb\"; return aaa + bbb;")); assertEquals("aaabbbbbbbbb", exec( "String aaa = \"aaa\", bbb = \"bbb\"; int x;\n" + "for (; x < 3; ++x) \n" + " aaa += bbb;\n" + "return aaa;")); } public void testComp() { assertEquals(true, exec("return 2 < 3;")); assertEquals(false, exec("int x = 4; char y = 2; return x < y;")); assertEquals(true, exec("return 3 <= 3;")); assertEquals(true, exec("int x = 3; char y = 3; return x <= y;")); assertEquals(false, exec("return 2 > 3;")); assertEquals(true, exec("int x = 4; long y = 2; return x > y;")); assertEquals(false, exec("return 3 >= 4;")); assertEquals(true, exec("double x = 3; float y = 3; return x >= y;")); assertEquals(false, exec("return 3 == 4;")); assertEquals(true, exec("double x = 3; float y = 3; return x == y;")); assertEquals(true, exec("return 3 != 4;")); assertEquals(false, exec("double x = 3; float y = 3; return x != y;")); } /** * Test boxed def objects in various places */ public void testBoxing() { // return assertEquals(4, exec("return params.get(\"x\");", Collections.singletonMap("x", 4), true)); // assignment assertEquals(4, exec("int y = params.get(\"x\"); return y;", Collections.singletonMap("x", 4), true)); // comparison assertEquals(true, exec("return 5 > params.get(\"x\");", Collections.singletonMap("x", 4), true)); } public void testBool() { assertEquals(true, exec("return true && true;")); assertEquals(false, exec("boolean a = true, b = false; return a && b;")); assertEquals(true, exec("return true || true;")); assertEquals(true, exec("boolean a = true, b = false; return a || b;")); } public void testConditional() { assertEquals(1, exec("int x = 5; return x > 3 ? 1 : 0;")); assertEquals(0, exec("String a = null; return a != null ? 1 : 0;")); } public void testPrecedence() { assertEquals(2, exec("int x = 5; return (x+x)/x;")); assertEquals(true, exec("boolean t = true, f = false; return t && (f || t);")); } public void testNullSafeDeref() { // Objects in general // Call assertNull( exec("String a = null; return a?.toString()")); assertEquals("foo", exec("String a = 'foo'; return a?.toString()")); assertNull( exec("def a = null; return a?.toString()")); assertEquals("foo", exec("def a = 'foo'; return a?.toString()")); // Call with primitive result assertMustBeNullable( "String a = null; return a?.length()"); assertMustBeNullable( "String a = 'foo'; return a?.length()"); assertNull( exec("def a = null; return a?.length()")); assertEquals(3, exec("def a = 'foo'; return a?.length()")); // Read shortcut assertMustBeNullable( "org.elasticsearch.painless.FeatureTest a = null; return a?.x"); assertMustBeNullable( "org.elasticsearch.painless.FeatureTest a = new org.elasticsearch.painless.FeatureTest(); return a?.x"); assertNull( exec("def a = null; return a?.x")); assertEquals(0, exec("def a = new org.elasticsearch.painless.FeatureTest(); return a?.x")); // Maps // Call assertNull( exec("Map a = null; return a?.toString()")); assertEquals("{}", exec("Map a = [:]; return a?.toString()")); assertNull( exec("def a = null; return a?.toString()")); assertEquals("{}", exec("def a = [:]; return a?.toString()")); // Call with primitive result assertMustBeNullable( "Map a = [:]; return a?.size()"); assertMustBeNullable( "Map a = null; return a?.size()"); assertNull( exec("def a = null; return a?.size()")); assertEquals(0, exec("def a = [:]; return a?.size()")); // Read shortcut assertNull( exec("Map a = null; return a?.other")); // Read shortcut assertEquals(1, exec("Map a = ['other':1]; return a?.other")); // Read shortcut assertNull( exec("def a = null; return a?.other")); // Read shortcut assertEquals(1, exec("def a = ['other':1]; return a?.other")); // Read shortcut // Array // Since you can't invoke methods on arrays we skip the toString and hashCode tests assertMustBeNullable("int[] a = null; return a?.length"); assertMustBeNullable("int[] a = new int[] {2, 3}; return a?.length"); assertNull( exec("def a = null; return a?.length")); assertEquals(2, exec("def a = new int[] {2, 3}; return a?.length")); // Results from maps (should just work but let's test anyway) FeatureTest t = new FeatureTest(); assertNull( exec("Map a = ['thing': params.t]; return a.other?.getX()", singletonMap("t", t), true)); assertNull( exec("Map a = ['thing': params.t]; return a.other?.x", singletonMap("t", t), true)); assertNull( exec("def a = ['thing': params.t]; return a.other?.getX()", singletonMap("t", t), true)); assertNull( exec("def a = ['thing': params.t]; return a.other?.x", singletonMap("t", t), true)); assertEquals(0, exec("Map a = ['other': params.t]; return a.other?.getX()", singletonMap("t", t), true)); assertEquals(0, exec("Map a = ['other': params.t]; return a.other?.x", singletonMap("t", t), true)); assertEquals(0, exec("def a = ['other': params.t]; return a.other?.getX()", singletonMap("t", t), true)); assertEquals(0, exec("def a = ['other': params.t]; return a.other?.x", singletonMap("t", t), true)); // Chains assertNull( exec("Map a = ['thing': ['cat': params.t]]; return a.other?.cat?.getX()", singletonMap("t", t), true)); assertNull( exec("Map a = ['thing': ['cat': params.t]]; return a.other?.cat?.x", singletonMap("t", t), true)); assertNull( exec("def a = ['thing': ['cat': params.t]]; return a.other?.cat?.getX()", singletonMap("t", t), true)); assertNull( exec("def a = ['thing': ['cat': params.t]]; return a.other?.cat?.x", singletonMap("t", t), true)); assertEquals(0, exec("Map a = ['other': ['cat': params.t]]; return a.other?.cat?.getX()", singletonMap("t", t), true)); assertEquals(0, exec("Map a = ['other': ['cat': params.t]]; return a.other?.cat?.x", singletonMap("t", t), true)); assertEquals(0, exec("def a = ['other': ['cat': params.t]]; return a.other?.cat?.getX()", singletonMap("t", t), true)); assertEquals(0, exec("def a = ['other': ['cat': params.t]]; return a.other?.cat?.x", singletonMap("t", t), true)); // Assignments assertNull(exec( "def a = [:];\n" + "a.missing_length = a.missing?.length();\n" + "return a.missing_length", true)); assertEquals(3, exec( "def a = [:];\n" + "a.missing = 'foo';\n" + "a.missing_length = a.missing?.length();\n" + "return a.missing_length", true)); // Writes, all unsupported at this point // assertEquals(null, exec("org.elasticsearch.painless.FeatureTest a = null; return a?.x")); // Read field // assertEquals(null, exec("org.elasticsearch.painless.FeatureTest a = null; a?.x = 7; return a?.x")); // Write field // assertEquals(null, exec("Map a = null; a?.other = 'wow'; return a?.other")); // Write shortcut // assertEquals(null, exec("def a = null; a?.other = 'cat'; return a?.other")); // Write shortcut // assertEquals(null, exec("Map a = ['thing': 'bar']; a.other?.cat = 'no'; return a.other?.cat")); // assertEquals(null, exec("def a = ['thing': 'bar']; a.other?.cat = 'no'; return a.other?.cat")); // assertEquals(null, exec("Map a = ['thing': 'bar']; a.other?.cat?.dog = 'wombat'; return a.other?.cat?.dog")); // assertEquals(null, exec("def a = ['thing': 'bar']; a.other?.cat?.dog = 'wombat'; return a.other?.cat?.dog")); } private void assertMustBeNullable(String script) { Exception e = expectScriptThrows(IllegalArgumentException.class, false, () -> exec(script)); assertEquals("Result of null safe operator must be nullable", e.getMessage()); } }