blob: 8f1f2567edfb51eb3910a59d60878f65ae2ff109 [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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.commons.jexl3.internal.LexicalScope;
import org.apache.commons.jexl3.internal.Script;
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(final boolean feature) throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(feature);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
final JexlEvalContext ctxt = new JexlEvalContext();
final 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 (final JexlException xany) {
final 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 (final JexlException xany) {
final String ww = xany.toString();
}
try {
script = jexl.createScript("var x = 0; for(var x : null) {};");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (final JexlException xany) {
final String ww = xany.toString();
}
try {
script = jexl.createScript("(x)->{ var x = 0; x; }");
if (!feature) {
script.execute(ctxt);
}
Assert.fail();
} catch (final JexlException xany) {
final 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 (final JexlException xany) {
final 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 (final JexlException xany) {
final String ww = xany.toString();
}
try {
script = jexl.createScript("(x)->{ for(var x : null) { x; } }");
if (!feature) {
script.execute(ctxt, 42);
}
Assert.fail();
} catch (final JexlException xany) {
final 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(final boolean shade) throws Exception {
final JexlEngine jexl = new JexlBuilder().strict(true).create();
final JexlEvalContext ctxt = new JexlEvalContext();
Object result;
ctxt.set("x", 4242);
final 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 (final 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 (final 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 (final 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 (final JexlException xany) {
if (!shade) {
throw xany;
}
}
}
@Test
public void testLexical1() throws Exception {
final JexlEngine jexl = new JexlBuilder().strict(true).create();
final JexlEvalContext ctxt = new JexlEvalContext();
final 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 (final JexlException xany) {
final 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 (final JexlException xany) {
final 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(final boolean lexical) throws Exception {
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(lexical).create();
final JexlContext ctxt = new MapContext();
final JexlScript script = jexl.createScript("{var x = 42}; {var x; return x; }");
final Object result = script.execute(ctxt);
if (lexical) {
Assert.assertNull(result);
} else {
Assert.assertEquals(42, result);
}
}
@Test
public void testLexical3() throws Exception {
final String str = "var s = {}; for (var i : [1]) s.add(i); s";
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
JexlScript e = jexl.createScript(str);
final 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 {
final JexlEngine Jexl = new JexlBuilder().silent(false).strict(true).lexical(true).create();
final JxltEngine Jxlt = Jexl.createJxltEngine();
final JexlContext ctxt = new MapContext();
final String rpt
= "<report>\n"
+ "\n$$var y = 1; var x = 2;"
+ "\n${x + y}"
+ "\n</report>\n";
final JxltEngine.Template t = Jxlt.createTemplate("$$", new StringReader(rpt));
final StringWriter strw = new StringWriter();
t.evaluate(ctxt, strw);
final String output = strw.toString();
final String ctl = "<report>\n\n3\n</report>\n";
Assert.assertEquals(ctl, output);
}
public static class DebugContext extends MapContext {
public DebugContext() {
}
public Object debug(final Object arg) {
return arg;
}
}
@Test
public void testLexical5() throws Exception {
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
final JexlContext ctxt = new DebugContext();
JexlScript script;
Object result;
script = jexl.createScript("var x = 42; var y = () -> { {var x = debug(-42); }; return x; }; y()");
try {
result = script.execute(ctxt);
Assert.assertEquals(42, result);
} catch (final JexlException xany) {
final String ww = xany.toString();
Assert.fail(ww);
}
}
@Test
public void testLexical6a() throws Exception {
final String str = "i = 0; { var i = 32; }; i";
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
final JexlScript e = jexl.createScript(str);
final JexlContext ctxt = new MapContext();
final Object o = e.execute(ctxt);
Assert.assertEquals(0, o);
}
@Test
public void testLexical6b() throws Exception {
final String str = "i = 0; { var i = 32; }; i";
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
final JexlScript e = jexl.createScript(str);
final JexlContext ctxt = new MapContext();
try {
final Object o = e.execute(ctxt);
Assert.fail("i should be shaded");
} catch (final JexlException xany) {
Assert.assertNotNull(xany);
}
}
@Test
public void testLexical6c() throws Exception {
final String str = "i = 0; for (var i : [42]) i; i";
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(false).create();
final JexlScript e = jexl.createScript(str);
final JexlContext ctxt = new MapContext();
final Object o = e.execute(ctxt);
Assert.assertEquals(0, o);
}
@Test
public void testLexical6d() throws Exception {
final String str = "i = 0; for (var i : [42]) i; i";
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
final JexlScript e = jexl.createScript(str);
final JexlContext ctxt = new MapContext();
try {
final Object o = e.execute(ctxt);
Assert.fail("i should be shaded");
} catch (final JexlException xany) {
Assert.assertNotNull(xany);
}
}
@Test
public void testPragmaOptions() throws Exception {
// same as 6d but using a pragma
final String str = "#pragma jexl.options '+strict +lexical +lexicalShade -safe'\n"
+ "i = 0; for (var i : [42]) i; i";
final JexlEngine jexl = new JexlBuilder().strict(false).create();
final JexlScript e = jexl.createScript(str);
final JexlContext ctxt = new MapContext();
try {
final Object o = e.execute(ctxt);
Assert.fail("i should be shaded");
} catch (final JexlException xany) {
Assert.assertNotNull(xany);
}
}
@Test
public void testPragmaNoop() throws Exception {
// unknow pragma
final String str = "#pragma jexl.options 'no effect'\ni = -42; for (var i : [42]) i; i";
final JexlEngine jexl = new JexlBuilder().lexical(false).strict(true).create();
final JexlScript e = jexl.createScript(str);
final JexlContext ctxt = new MapContext();
final Object result = e.execute(ctxt);
Assert.assertEquals(42, result);
}
@Test
public void testScopeFrame() throws Exception {
final LexicalScope scope = new LexicalScope();
for(int i = 0; i < 128; i += 2) {
Assert.assertTrue(scope.addSymbol(i));
Assert.assertFalse(scope.addSymbol(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 {
final JexlFeatures f= new JexlFeatures();
final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
final JexlEvalContext ctxt = new JexlEvalContext();
final JexlOptions options = ctxt.getEngineOptions();
options.setSharedInstance(false);
options.setLexical(true);
options.setLexicalShade(true);
ctxt.set("options", options);
final JexlScript script = jexl.createScript("{var x = 42;} options.lexical = false; options.lexicalShade=false; x");
try {
final Object result = script.execute(ctxt);
Assert.fail("setting options.lexical should have no effect during execution");
} catch(final JexlException xf) {
Assert.assertNotNull(xf);
}
}
/**
* Context augmented with a tryCatch.
*/
public static class TestContext extends JexlEvalContext {
public TestContext() {}
public TestContext(final 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(final JexlScript tryFn, final JexlScript catchFn, final Object... args) {
Object result;
try {
result = tryFn.execute(this, args);
} catch (final Throwable xthrow) {
result = catchFn != null ? catchFn.execute(this, xthrow) : xthrow;
}
return result;
}
}
@Test
public void testContextualOptions1() throws Exception {
final JexlFeatures f = new JexlFeatures();
final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
final JexlEvalContext ctxt = new TestContext();
final JexlOptions options = ctxt.getEngineOptions();
options.setSharedInstance(true);
options.setLexical(true);
options.setLexicalShade(true);
ctxt.set("options", options);
final JexlScript runner = jexl.createScript(
"options.lexical = flag; options.lexicalShade = flag;"
+ "tryCatch(test, catch, 42);",
"flag", "test", "catch");
final JexlScript tested = jexl.createScript("(y)->{ {var x = y;} x }");
final 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);
}
@Test
public void testParameter0() throws Exception {
final String str = "function(u) {}";
final JexlEngine jexl = new JexlBuilder().create();
JexlScript e = jexl.createScript(str);
Assert.assertEquals(1, e.getParameters().length);
e = jexl.createScript(new JexlInfo("TestScript", 1, 1), str);
Assert.assertEquals(1, e.getParameters().length);
}
@Test
public void testParameter1() throws Exception {
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
final JexlContext jc = new MapContext();
final String strs = "var s = function(x) { for (var i : 1..3) {if (i > 2) return x}}; s(42)";
final JexlScript s42 = jexl.createScript(strs);
final Object result = s42.execute(jc);
Assert.assertEquals(42, result);
}
@Test
public void testInnerAccess0() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
final JexlScript script = jexl.createScript(
"var x = 32; ("
+ "()->{ for(var x : null) { var c = 0; {return x; }} })"
+ "();");
Assert.assertNull(script.execute(null));
}
@Test
public void testInnerAccess1() throws Exception {
final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
final JexlScript script = jexl.createScript("var x = 32; (()->{ for(var x : null) { var c = 0; {return x; }} })();");
}
@Test
public void testForVariable0() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
f.lexicalShade(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
try {
final JexlScript script = jexl.createScript("for(var x : 1..3) { var c = 0}; return x");
Assert.fail("Should not have been parsed");
} catch (final JexlException ex) {
// OK
}
}
@Test
public void testForVariable1() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
f.lexicalShade(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
try {
final JexlScript script = jexl.createScript("for(var x : 1..3) { var c = 0} for(var x : 1..3) { var c = 0}; return x");
Assert.fail("Should not have been parsed");
} catch (final JexlException ex) {
// OK
Assert.assertTrue(ex instanceof JexlException);
}
}
@Test
public void testUndeclaredVariable() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
f.lexicalShade(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
try {
final JexlScript script = jexl.createScript("{var x = 0}; return x");
Assert.fail("Should not have been parsed");
} catch (final Exception ex) {
// OK
Assert.assertTrue(ex instanceof JexlException);
}
}
@Test
public void testLexical6a1() throws Exception {
final String str = "i = 0; { var i = 32; }; i";
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
final JexlScript e = jexl.createScript(str);
final JexlContext ctxt = new MapContext();
final Object o = e.execute(ctxt);
Assert.assertEquals(0, o);
}
public static class VarContext extends MapContext implements JexlContext.PragmaProcessor, JexlContext.OptionsHandle {
private JexlOptions options = new JexlOptions();
JexlOptions snatchOptions() {
final JexlOptions o = options;
options = new JexlOptions();
return o;
}
@Override
public void processPragma(final String key, final Object value) {
if ("jexl.options".equals(key) && "canonical".equals(value)) {
options.setStrict(true);
options.setLexical(true);
options.setLexicalShade(true);
options.setSafe(false);
}
}
@Override
public JexlOptions getEngineOptions() {
return options;
}
}
@Test
public void testInternalLexicalFeatures() throws Exception {
final String str = "42";
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
f.lexicalShade(true);
final JexlEngine jexl = new JexlBuilder().features(f).create();
final JexlScript e = jexl.createScript(str);
final VarContext vars = new VarContext();
final JexlOptions opts = vars.getEngineOptions();
// so we can see the effect of features on options
opts.setSharedInstance(true);
final Script script = (Script) e;
final JexlFeatures features = script.getFeatures();
Assert.assertTrue(features.isLexical());
Assert.assertTrue(features.isLexicalShade());
final Object result = e.execute(vars);
Assert.assertEquals(42, result);
Assert.assertTrue(opts.isLexical());
Assert.assertTrue(opts.isLexicalShade());
}
@Test
public void testOptionsPragma() throws Exception {
try {
JexlOptions.setDefaultFlags("+safe", "-lexical", "-lexicalShade");
final VarContext vars = new VarContext();
final JexlEngine jexl = new JexlBuilder().create();
int n42;
JexlOptions o;
n42 = (Integer) jexl.createScript("#pragma jexl.options none\n-42").execute(vars);
Assert.assertEquals(-42, n42);
o = vars.snatchOptions();
Assert.assertNotNull(o);
Assert.assertTrue(o.isStrict());
Assert.assertTrue(o.isSafe());
Assert.assertTrue(o.isCancellable());
Assert.assertFalse(o.isLexical());
Assert.assertFalse(o.isLexicalShade());
n42 = (Integer) jexl.createScript("#pragma jexl.options canonical\n42").execute(vars);
Assert.assertEquals(42, n42);
o = vars.snatchOptions();
Assert.assertNotNull(o);
Assert.assertTrue(o.isStrict());
Assert.assertFalse(o.isSafe());
Assert.assertTrue(o.isCancellable());
Assert.assertTrue(o.isLexical());
Assert.assertTrue(o.isLexicalShade());
Assert.assertFalse(o.isSharedInstance());
} finally {
JexlOptions.setDefaultFlags("-safe", "+lexical");
}
}
@Test
public void testVarLoop0() throws Exception {
final String src0 = "var count = 10;\n"
+ "for (var i : 0 .. count-1) {\n"
+ " $out.add(i);\n"
+ "}";
final String src1 = "var count = [0,1,2,3,4,5,6,7,8,9];\n"
+ "for (var i : count) {\n"
+ " $out.add(i);\n"
+ "}";
final String src2 = "var count = 10;\n" +
" var outer = 0;\n"
+ "for (var i : 0 .. count-1) {\n"
+ " $out.add(i);\n"
+ " outer = i;"
+ "}\n"
+ "outer == 9";
final JexlFeatures ff0 = runVarLoop(false, src0);
final JexlFeatures ft0= runVarLoop(true, src0);
final JexlFeatures ff1 = runVarLoop(false, src1);
final JexlFeatures ft1= runVarLoop(true, src1);
final JexlFeatures ff2 = runVarLoop(false, src2);
final JexlFeatures ft2= runVarLoop(true, src2);
// and check some features features
Assert.assertEquals(ff0, ff1);
Assert.assertEquals(ft0, ft1);
Assert.assertNotEquals(ff0, ft0);
final String sff0 = ff0.toString();
final String sff1 = ff1.toString();
Assert.assertEquals(sff0, sff1);
final String sft1 = ft1.toString();
Assert.assertNotEquals(sff0, sft1);
}
private JexlFeatures runVarLoop(final boolean flag, final String src) throws Exception {
final VarContext vars = new VarContext();
final JexlOptions options = vars.getEngineOptions();
options.setLexical(true);
options.setLexicalShade(true);
options.setSafe(false);
final JexlFeatures features = new JexlFeatures();
if (flag) {
features.lexical(true).lexicalShade(true);
}
final JexlEngine jexl = new JexlBuilder().features(features).create();
final JexlScript script = jexl.createScript(src);
final List<Integer> out = new ArrayList<Integer>(10);
vars.set("$out", out);
final Object result = script.execute(vars);
Assert.assertEquals(true, result);
Assert.assertEquals(10, out.size());
return features;
}
public static class OptAnnotationContext extends JexlEvalContext implements JexlContext.AnnotationProcessor {
@Override
public Object processAnnotation(final String name, final Object[] args, final Callable<Object> statement) throws Exception {
// transient side effect for strict
if ("scale".equals(name)) {
final JexlOptions options = this.getEngineOptions();
final int scale = options.getMathScale();
final int newScale = (Integer) args[0];
options.setMathScale(newScale);
try {
return statement.call();
} finally {
options.setMathScale(scale);
}
}
return statement.call();
}
}
@Test
public void testAnnotation() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
final JexlScript script = jexl.createScript("@scale(13) @test var i = 42");
final JexlContext jc = new OptAnnotationContext();
final Object result = script.execute(jc);
Assert.assertEquals(42, result);
}
@Test
public void testNamed() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
final JexlScript script = jexl.createScript("var i = (x, y, z)->{return x + y + z}; i(22,18,2)");
final JexlContext jc = new MapContext();
final Object result = script.execute(jc);
Assert.assertEquals(42, result);
}
@Test
public void tesstCaptured0() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
final JexlScript script = jexl.createScript(
"var x = 10; (b->{ x + b })(32)");
final JexlContext jc = new MapContext();
final Object result = script.execute(jc);
Assert.assertEquals(42, result);
}
@Test
public void testCaptured1() throws Exception {
final JexlFeatures f = new JexlFeatures();
f.lexical(true);
final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
final JexlScript script = jexl.createScript(
"{var x = 10; } (b->{ x + b })(32)");
final JexlContext jc = new MapContext();
jc.set("x", 11);
final Object result = script.execute(jc);
Assert.assertEquals(43, result);
}
}