Java tutorial
/* * xTest * Copyright (C) 2014 Stefano Fornari * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY Stefano Fornari, Stefano Fornari * DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. */ package ste.xtest.js; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import javassist.NotFoundException; import javax.script.ScriptException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.UniqueTag; import ste.xtest.junit.BugFree; /** * Base class for junit tests for JavaSctript scripts. It provides a simple * framework to work with engine inside JUnit. JavaScript test cases shall * inherit from this base class and take advantage of the facility provided. * <p/> * It provides two useful methods to invoke a method defined in a javascript * file: * <ul> * <li><code>String call(String method, String... args)</code>: to use if the * invoked method returns a String and has just String parameters</li> * <li><code>Object call(String method, Object... args)</code>: generic version * of the previous one. It can be used to invoke any method</li> * </ul> * <p/> * Below a simple example of usage to test method * <code>replaceString(String arg1, String arg2, String arg3)</code> defined in * <code>replace.js</code> script file. * <blockquote><pre> * public class ReplaceTest extends JavaScriptTest { * * public ReplaceTest() { * fileName = "<SOMEWHERE>/replace.js" * } * * @Test * public void replace() throws Throwable { * String method = "replaceString"; * // * // replace arg2 with arg3 in arg1 * // * String arg1 = "this is my cat"; * String arg2 = "cat"; * String arg3 = "dog"; * * String result = call(method, arg1, arg2, arg3); * * assertEquals("this is my dog", result); * } * * } * </pre></blockquote> * */ public abstract class BugFreeJavaScript extends BugFree { // // TODO: extract common base class between JavaScriptTest and BeanShellTest // // ---------------------------------------------------------- Protected data /** * The JavaScript scope scripts are executed into */ protected Scriptable scope; // ------------------------------------------------------------ Constructors /** * Creates a new JavaScripttest * * @throws ScriptException if any setup script could not be loaded. */ public BugFreeJavaScript() throws ScriptException { scope = null; Context cx = Context.enter(); scope = cx.initStandardObjects(); cx.setOptimizationLevel(-1); // // xtest initialization // loadResourceScripts(cx, new String[] { "/js/xtest.init.js", "/js/env.rhino.1.2.js", "/js/sprintf-0.0.7.min.js", "/js/jquery-1.11.1.min.js", "/js/xtest.setup.js", "/js/urlsearchparams.js" }); } // ---------------------------------------------------------- Public methods /** * Returns an object as defined in the current script scope * * @param name the object name (variable, function, ...) - NOT NULL * * @return the corresponding object * * @throws IllegalArgumentException if name is null */ public Object get(String name) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("name can not be blank"); } Object ret = scope.get(name, scope); if (UniqueTag.NOT_FOUND.equals(ret)) { return null; } return ret; } /** * Sets a variable in the current script scope with given name and value * * @param name the object name (variable, function, ...) - NOT BLANK * @param value the value - MAY BE NULL */ public void set(final String name, Object value) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("name can not be blank"); } scope.put(name, scope, value); } /** * Load the given script from the file system (first) or the class path. * * @param script the script file/resource name - NOT NULL * * @throws IllegalArgumentException if fileName is null * @throws IOException in case of IO errors * */ public void loadScript(String script) throws IOException { if (script == null) { throw new IllegalArgumentException("script cannot be null"); } // // let's try to read the script from the file system first; if an io // exception is thrown, then we try from the classpath // Context cx = Context.enter(); Reader r = null; try { r = new FileReader(script); } catch (FileNotFoundException x) { InputStream is = getClass().getResourceAsStream(script); if (is == null) { throw x; } r = new InputStreamReader(is); } finally { if (r != null) { cx.evaluateReader(scope, r, script, 1, null); r.close(); } Context.exit(); } } /** * Load the given fixture. The fixture is basically an html fragment which * will be wrapped inside a: * <code> * $("body").append(... html ...); * <code> * Double quotes will be escaped. * * @param fixture the fixture file name - NOT NULL * * @throws IllegalArgumentException if fixtureis null * @throws NotFoundException if the fixture is not found * @throws IOException in case of IO errors * */ public void loadFixture(String fixture) throws IOException { if (fixture == null) { throw new IllegalArgumentException("fixture cannot be null"); } String script = String.format("$(\"body\").append(\"%s\");", StringEscapeUtils.escapeJava(FileUtils.readFileToString(new File(fixture)))); exec(script); } // ------------------------------------------------------- Protected methods /** * Exec the given function assuming it is defined in the current script * scope * * @param name the function to invoke * @param args the arguments of the method * @return the object returned by the invoked function * * @throws java.lang.Throwable if an error occurs * @throws IllegalArgumentException if name is not a function */ protected Object call(String name, Object... args) throws Throwable { Object o = scope.get(name, scope); if (!(o instanceof Function)) { throw new IllegalArgumentException(name + " is undefined or not a function."); } Function f = (Function) o; Context cx = Context.enter(); Object result = f.call(cx, scope, scope, args); Context.exit(); return result; } /** * Exec the given script assuming it is defined in the current script * scope * * @param script the script to execute - NOT NULL * @return the object returned by execution of the script * * @throws IllegalArgumentException if script is null */ protected Object exec(String script) { if (script == null) { throw new IllegalArgumentException("script cannot be null"); } Context cx = Context.enter(); Object result = cx.evaluateString(scope, script, "script", 1, null); Context.exit(); return result; } // --------------------------------------------------------- Private methods private void loadResourceScripts(final Context cx, final String[] scripts) throws ScriptException { InputStream is = null; try { for (String script : scripts) { is = BugFreeJavaScript.class.getResourceAsStream(script); if (is == null) { throw new FileNotFoundException(script + " not found in classpath"); } cx.evaluateReader(scope, new InputStreamReader(is), script, 1, null); is.close(); is = null; } } catch (Exception x) { throw new ScriptException("Error initializing the javascript engine: " + x.getMessage()); } finally { Context.exit(); if (is != null) { try { is.close(); } catch (Exception x) { } } ; } } }