blob: 2c6b2f7fe35bb5fd9f96bcbab8c86a7cb532af41 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.
*/
package org.apache.commons.jexl3;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;
import java.util.Set;
import org.apache.commons.jexl3.internal.LexicalScope;
import org.junit.Assert;
import org.junit.Test;
/**
* Test cases for lexical option and feature.
*/
public class LexicalTest {
@Test
public void testLexical0a() throws Exception {
runLexical0(false);
}
@Test
public void testLexical0b() throws Exception {
runLexical0(true);
}
void runLexical0(boolean feature) throws Exception {
JexlFeatures f = new JexlFeatures();
f.lexical(feature);
JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
JexlEvalContext ctxt = new JexlEvalContext();
JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
options.setLexical(true);
JexlScript script;
try {
script = jexl.createScript("var x = 0; var x = 1;");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
try {
script = jexl.createScript("var x = 0; for(var y : null) { var y = 1;");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
try {
script = jexl.createScript("var x = 0; for(var x : null) {};");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
try {
script = jexl.createScript("(x)->{ var x = 0; x; }");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
try {
script = jexl.createScript("var x; if (true) { if (true) { var x = 0; x; } }");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
try {
script = jexl.createScript("if (a) { var y = (x)->{ var x = 0; x; }; y(2) }", "a");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
try {
script = jexl.createScript("(x)->{ for(var x : null) { x; } }");
if (!feature) {
script.execute(ctxt, 42);
}
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
// no fail
script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
if (!feature) {
script.execute(ctxt, 42);
}
}
@Test
public void testLexical1a() throws Exception {
runLexical1(false);
}
@Test
public void testLexical1b() throws Exception {
runLexical1(true);
}
void runLexical1(boolean shade) throws Exception {
JexlEngine jexl = new JexlBuilder().strict(true).create();
JexlEvalContext ctxt = new JexlEvalContext();
Object result;
ctxt.set("x", 4242);
JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
options.setLexical(true);
options.setLexicalShade(shade);
JexlScript script;
try {
// if local shade, x is undefined
script = jexl.createScript("{ var x = 0; } x");
script.execute(ctxt);
if (shade) {
Assert.fail("local shade means 'x' should be undefined");
}
} catch (JexlException xany) {
if (!shade) {
throw xany;
}
}
try {
// if local shade, x = 42 is undefined
script = jexl.createScript("{ var x = 0; } x = 42");
script.execute(ctxt);
if (shade) {
Assert.fail("local shade means 'x = 42' should be undefined");
}
} catch (JexlException xany) {
if (!shade) {
throw xany;
}
}
try {
// if local shade, x = 42 is undefined
script = jexl.createScript("{ var x = 0; } y = 42");
script.execute(ctxt);
if (shade) {
Assert.fail("local shade means 'y = 42' should be undefined (y is undefined)");
}
} catch (JexlException xany) {
if (!shade) {
throw xany;
}
}
// no fail
script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
//if (!feature) {
script.execute(ctxt, 42);
//}
// y being defined as global
ctxt.set("y", 4242);
try {
// if no shade and global y being defined,
script = jexl.createScript("{ var y = 0; } y = 42");
result = script.execute(ctxt);
if (!shade) {
Assert.assertEquals(42, result);
} else {
Assert.fail("local shade means 'y = 42' should be undefined");
}
} catch (JexlException xany) {
if (!shade) {
throw xany;
}
}
}
@Test
public void testLexical1() throws Exception {
JexlEngine jexl = new JexlBuilder().strict(true).create();
JexlEvalContext ctxt = new JexlEvalContext();
JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
options.setLexical(true);
JexlScript script;
Object result;
script = jexl.createScript("var x = 0; for(var y : [1]) { var x = 42; return x; };");
try {
result = script.execute(ctxt);
//Assert.assertEquals(42, result);
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
try {
script = jexl.createScript("(x)->{ if (x) { var x = 7 * (x + x); x; } }");
result = script.execute(ctxt, 3);
Assert.fail();
} catch (JexlException xany) {
String ww = xany.toString();
}
script = jexl.createScript("{ var x = 0; } var x = 42; x");
result = script.execute(ctxt, 21);
Assert.assertEquals(42, result);
}
@Test
public void testLexical2a() throws Exception {
runLexical2(true);
}
@Test
public void testLexical2b() throws Exception {
runLexical2(false);
}
protected void runLexical2(boolean lexical) throws Exception {
JexlEngine jexl = new JexlBuilder().strict(true).lexical(lexical).create();
JexlContext ctxt = new MapContext();
JexlScript script = jexl.createScript("{var x = 42}; {var x; return x; }");
Object result = script.execute(ctxt);
if (lexical) {
Assert.assertNull(result);
} else {
Assert.assertEquals(42, result);
}
}
@Test
public void testLexical3() throws Exception {
String str = "var s = {}; for (var i : [1]) s.add(i); s";
JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
JexlScript e = jexl.createScript(str);
JexlContext jc = new MapContext();
Object o = e.execute(jc);
Assert.assertTrue(((Set)o).contains(1));
e = jexl.createScript(str);
o = e.execute(jc);
Assert.assertTrue(((Set)o).contains(1));
}
@Test
public void testLexical4() throws Exception {
JexlEngine Jexl = new JexlBuilder().silent(false).strict(true).lexical(true).create();
JxltEngine Jxlt = Jexl.createJxltEngine();
JexlContext ctxt = new MapContext();
String rpt
= "<report>\n"
+ "\n$$var y = 1; var x = 2;"
+ "\n${x + y}"
+ "\n</report>\n";
JxltEngine.Template t = Jxlt.createTemplate("$$", new StringReader(rpt));
StringWriter strw = new StringWriter();
t.evaluate(ctxt, strw);
String output = strw.toString();
String ctl = "<report>\n\n3\n</report>\n";
Assert.assertEquals(ctl, output);
}
@Test
public void testLexical5() throws Exception {
JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
JexlContext ctxt = new MapContext();
JexlScript script;
Object result;
script = jexl.createScript("var x = 42; var z = 169; var y = () -> { {var x = -42; }; return x; }; y()");
try {
result = script.execute(ctxt);
Assert.assertEquals(42, result);
} catch (JexlException xany) {
String ww = xany.toString();
Assert.fail(ww);
}
}
@Test
public void testLexical6a() throws Exception {
String str = "i = 0; { var i = 32; }; i";
JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
JexlScript e = jexl.createScript(str);
JexlContext ctxt = new MapContext();
Object o = e.execute(ctxt);
Assert.assertEquals(0, o);
}
@Test
public void testLexical6b() throws Exception {
String str = "i = 0; { var i = 32; }; i";
JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
JexlScript e = jexl.createScript(str);
JexlContext ctxt = new MapContext();
try {
Object o = e.execute(ctxt);
Assert.fail("i should be shaded");
} catch (JexlException xany) {
Assert.assertNotNull(xany);
}
}
@Test
public void testLexical6c() throws Exception {
String str = "i = 0; for (var i : [42]) i; i";
JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(false).create();
JexlScript e = jexl.createScript(str);
JexlContext ctxt = new MapContext();
Object o = e.execute(ctxt);
Assert.assertEquals(0, o);
}
@Test
public void testLexical6d() throws Exception {
String str = "i = 0; for (var i : [42]) i; i";
JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
JexlScript e = jexl.createScript(str);
JexlContext ctxt = new MapContext();
try {
Object o = e.execute(ctxt);
Assert.fail("i should be shaded");
} catch (JexlException xany) {
Assert.assertNotNull(xany);
}
}
@Test
public void testPragmaOptions() throws Exception {
// same as 6d but using a pragma
String str = "#pragma jexl.options '+strict +lexical +lexicalShade -safe'\n"
+ "i = 0; for (var i : [42]) i; i";
JexlEngine jexl = new JexlBuilder().strict(false).create();
JexlScript e = jexl.createScript(str);
JexlContext ctxt = new MapContext();
try {
Object o = e.execute(ctxt);
Assert.fail("i should be shaded");
} catch (JexlException xany) {
Assert.assertNotNull(xany);
}
}
@Test
public void testPragmaNoop() throws Exception {
// unknow pragma
String str = "#pragma jexl.options 'no effect'\ni = -42; for (var i : [42]) i; i";
JexlEngine jexl = new JexlBuilder().lexical(false).strict(true).create();
JexlScript e = jexl.createScript(str);
JexlContext ctxt = new MapContext();
Object result = e.execute(ctxt);
Assert.assertEquals(42, result);
}
@Test
public void testScopeFrame() throws Exception {
LexicalScope scope = new LexicalScope(null);
for(int i = 0; i < 128; i += 2) {
Assert.assertTrue(scope.declareSymbol(i));
Assert.assertFalse(scope.declareSymbol(i));
}
for(int i = 0; i < 128; i += 2) {
Assert.assertTrue(scope.hasSymbol(i));
Assert.assertFalse(scope.hasSymbol(i + 1));
}
}
@Test
public void testContextualOptions0() throws Exception {
JexlFeatures f= new JexlFeatures();
JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
JexlEvalContext ctxt = new JexlEvalContext();
JexlOptions options = ctxt.getEngineOptions();
options.setSharedInstance(false);
options.setLexical(true);
options.setLexicalShade(true);
ctxt.set("options", options);
JexlScript script = jexl.createScript("{var x = 42;} options.lexical = false; options.lexicalShade=false; x");
try {
Object result = script.execute(ctxt);
Assert.fail("setting options.lexical should have no effect during execution");
} catch(JexlException xf) {
Assert.assertNotNull(xf);
}
}
/**
* Context augmented with a tryCatch.
*/
public static class TestContext extends JexlEvalContext {
public TestContext() {}
public TestContext(Map<String, Object> map) {
super(map);
}
/**
* Allows calling a script and catching its raised exception.
* @param tryFn the lambda to call
* @param catchFn the lambda catching the exception
* @param args the arguments to the lambda
* @return the tryFn result or the catchFn if an exception was raised
*/
public Object tryCatch(JexlScript tryFn, JexlScript catchFn, Object... args) {
Object result;
try {
result = tryFn.execute(this, args);
} catch (Throwable xthrow) {
result = catchFn != null ? catchFn.execute(this, xthrow) : xthrow;
}
return result;
}
}
@Test
public void testContextualOptions1() throws Exception {
JexlFeatures f = new JexlFeatures();
JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
JexlEvalContext ctxt = new TestContext();
JexlOptions options = ctxt.getEngineOptions();
options.setSharedInstance(true);
options.setLexical(true);
options.setLexicalShade(true);
ctxt.set("options", options);
JexlScript runner = jexl.createScript(
"options.lexical = flag; options.lexicalShade = flag;"
+ "tryCatch(test, catch, 42);",
"flag", "test", "catch");
JexlScript tested = jexl.createScript("(y)->{ {var x = y;} x }");
JexlScript catchFn = jexl.createScript("(xany)-> { xany }");
Object result;
// run it once, old 3.1 semantics, lexical/shade = false
result = runner.execute(ctxt, false, tested, catchFn);
// result 42
Assert.assertEquals(42, result);
// run it a second time, new 3.2 semantics, lexical/shade = true
result = runner.execute(ctxt, true, tested, catchFn);
// result is exception!
Assert.assertTrue(result instanceof JexlException.Variable);
}
}