All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ste.xtest.js.BugFreeJavaScript Maven / Gradle / Ivy

/*
 * 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 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.
 * 

* It provides two useful methods to invoke a method defined in a javascript * file: *

    *
  • String call(String method, String... args): to use if the * invoked method returns a String and has just String parameters
  • *
  • Object call(String method, Object... args): generic version * of the previous one. It can be used to invoke any method
  • *
*

* Below a simple example of usage to test method * replaceString(String arg1, String arg2, String arg3) defined in * replace.js script file. *

 * public class ReplaceTest extends JavaScriptTest {
 *
 *     public ReplaceTest() {
 *         fileName =  "/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);
 *     }
 *
 * }
 * 
* */ 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: * * $("body").append(... html ...); * * 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) { } }; } } }