Adding JUnit JavaScript Support
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/.classpath b/bundles/org.eclipse.e4.languages.javascript.junit/.classpath
new file mode 100644
index 0000000..2fbb7a2
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/.project b/bundles/org.eclipse.e4.languages.javascript.junit/.project
new file mode 100644
index 0000000..d3d99e8
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.e4.languages.javascript.junit</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.e4.languages.javascript.junit/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..2432afb
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Wed Apr 14 14:17:15 EDT 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/.settings/org.eclipse.pde.core.prefs b/bundles/org.eclipse.e4.languages.javascript.junit/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 0000000..b2d4e7c
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,4 @@
+#Wed Apr 07 17:49:08 EDT 2010
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.languages.javascript.junit/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..82d401e
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: JUnit JavaScript Support
+Bundle-SymbolicName: org.eclipse.e4.languages.javascript.junit
+Bundle-Version: 1.0.0.qualifier
+Bundle-RequiredExecutionEnvironment: J2SE-1.4
+Import-Package: junit.framework,
+ org.mozilla.javascript
+Export-Package: org.eclipse.e4.languages.javascript.junit
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/build.properties b/bundles/org.eclipse.e4.languages.javascript.junit/build.properties
new file mode 100644
index 0000000..34d2e4d
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptAssertionFailedError.java b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptAssertionFailedError.java
new file mode 100644
index 0000000..fa69fab
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptAssertionFailedError.java
@@ -0,0 +1,47 @@
+package org.eclipse.e4.languages.javascript.junit;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.AssertionFailedError;
+
+import org.mozilla.javascript.EvaluatorException;
+
+public class JavaScriptAssertionFailedError extends AssertionFailedError {
+
+	private static final long serialVersionUID = 8518724972493487259L;
+
+	public JavaScriptAssertionFailedError(AssertionFailedError e) {
+		super(e.getMessage());
+		initCause(e);
+		initStackTrace();
+	}
+
+	private void initStackTrace() {
+		EvaluatorException jsException = new EvaluatorException(null);
+		List targetTrace = new ArrayList();
+
+		StackTraceElement[] traceElements = jsException.getStackTrace();
+		for (int i = 0; i < traceElements.length; i++) {
+			StackTraceElement traceElement = traceElements[i];
+			if (!filter(traceElement))
+				targetTrace.add(new StackTraceElement("[JavaScript]", "", traceElement.getFileName(), traceElement.getLineNumber()));
+		}
+
+		setStackTrace((StackTraceElement[]) targetTrace.toArray(new StackTraceElement[targetTrace.size()]));
+	}
+
+	private boolean filter(StackTraceElement traceElement) {
+		if (!traceElement.getClassName().startsWith("org.mozilla.javascript"))
+			return true;
+		if (traceElement.getLineNumber() < 1)
+			return true;
+
+		if (traceElement.getFileName().endsWith(".java"))
+			return true;
+		if (traceElement.getFileName().startsWith("JavaScriptTestCase_"))
+			return true;
+
+		return false;
+	}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptTestCase.java b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptTestCase.java
new file mode 100644
index 0000000..fb81ccc
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptTestCase.java
@@ -0,0 +1,200 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.e4.languages.javascript.junit;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+public class JavaScriptTestCase extends TestCase {
+
+	private Context context;
+	private ScriptableObject globalScope;
+
+	private String testCaseName;
+	private Collection scripts;
+	private ClassLoader applicationClassLoader;
+	private ClassLoader originalApplicationClassLoader;
+	private boolean superRunTest = false;
+	private static URL ASSERT_SCRIPT = JavaScriptTestCase.class.getResource("assert.js");
+
+	public JavaScriptTestCase() {
+		this(null, null, null, null);
+	}
+
+	public JavaScriptTestCase(String name, String testCaseName) {
+		this(name, testCaseName, null, null);
+	}
+
+	public JavaScriptTestCase(String name, String testCaseName, ClassLoader applicationClassLoader) {
+		this(name, testCaseName, null, applicationClassLoader);
+	}
+
+	public JavaScriptTestCase(String name, String testCaseName, Collection scripts, ClassLoader applicationClassLoader) {
+		super(name);
+		this.testCaseName = testCaseName;
+		this.scripts = scripts;
+		this.applicationClassLoader = applicationClassLoader;
+	}
+
+	protected void setUp() throws Exception {
+		super.setUp();
+		context = Context.enter();
+		if (applicationClassLoader != null) {
+			originalApplicationClassLoader = context.getApplicationClassLoader();
+			context.setApplicationClassLoader(applicationClassLoader);
+		}
+		globalScope = context.initStandardObjects();
+		eval(readContents(ASSERT_SCRIPT), "JavaScriptTestCase_assert");
+		ScriptableObject assertScope = (ScriptableObject) ScriptableObject.getProperty(globalScope, "Assert");
+		ScriptableObject.putProperty(assertScope, "fail", new BaseFunction() { //$NON-NLS-1$
+					public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+						try {
+							if (args.length > 0) {
+								fail(Context.toString(args[0]));
+							} else {
+								fail();
+							}
+							return null;
+						} catch (AssertionFailedError e) {
+							throw new JavaScriptAssertionFailedError(e);
+						}
+					}
+				});
+		evalScripts();
+	}
+
+	protected Scriptable createJavaScriptTestCaseInstance(String testName) {
+		Scriptable currentScope = getGlobalScope();
+		StringTokenizer tokenizer = new StringTokenizer(testCaseName, "."); //$NON-NLS-1$
+		while (true) {
+			String token = tokenizer.nextToken();
+			Object value = currentScope.get(token, currentScope);
+			if (!tokenizer.hasMoreTokens()) {
+				BaseFunction constructor = (BaseFunction) value;
+				Object[] arguments = (testName==null) ? new Object[0]: new Object[]{testName};
+				return constructor.construct(context, getGlobalScope(), arguments);
+			}
+			if (value instanceof Scriptable)
+				currentScope = (Scriptable) value;
+			else
+				throw new RuntimeException("Not Found: " + testCaseName + " in " + this.toString()); //$NON-NLS-1$//$NON-NLS-2$
+		}
+	}
+
+	protected void tearDown() throws Exception {
+		if (applicationClassLoader != null) {
+			context.setApplicationClassLoader(originalApplicationClassLoader);
+			originalApplicationClassLoader = null;
+		}
+		globalScope = null;
+		context = null;
+		Context.exit();
+		super.tearDown();
+	}
+
+	public void useJavaTests() {
+		superRunTest = true;
+	}
+
+	public ScriptableObject getGlobalScope() {
+		return globalScope;
+	}
+
+	public Object eval(String source) {
+		return eval(source, null);
+	}
+
+	public Object eval(String source, String sourceName) {
+		if (sourceName == null) {
+			sourceName = "eval";
+		}
+		return context.evaluateString(globalScope, source, sourceName, 1, null);
+	}
+
+	protected void runTest() throws Throwable {
+		if (superRunTest || getName() == null) {
+			super.runTest();
+			return;
+		}
+		ScriptableObject testCaseInstance = (ScriptableObject) createJavaScriptTestCaseInstance(getName());
+		BaseFunction runMethod = (BaseFunction) ScriptableObject.getProperty(testCaseInstance, "run");
+		runMethod.call(context, globalScope, testCaseInstance, new Object[0]);
+	}
+
+	private void evalScripts() {
+		if (scripts == null)
+			return;
+		for (Iterator iterator = scripts.iterator(); iterator.hasNext();) {
+			Object script = iterator.next();
+			if (script instanceof String)
+				eval((String) script, null);
+			else if (script instanceof File) {
+				File scriptFile = (File) script;
+				eval(readContents(scriptFile), scriptFile.getAbsolutePath());
+			} else if (script instanceof URL) {
+				URL scriptURL = (URL) script;
+				eval(readContents(scriptURL), scriptURL.toExternalForm());
+			}
+		}
+	}
+
+	public static String readContents(File scriptFile) {
+		try {
+			return readContents(new FileInputStream(scriptFile));
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static String readContents(URL url) {
+		try {
+			return readContents(url.openStream());
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static String readContents(InputStream is) throws IOException {
+		Reader reader = new InputStreamReader(new BufferedInputStream(is));
+		try {
+			StringBuffer buffer = new StringBuffer();
+			int read = 0;
+			char[] cbuf = new char[1024];
+			while (-1 != (read = reader.read(cbuf))) {
+				buffer.append(cbuf, 0, read);
+			}
+			return buffer.toString();
+		} finally {
+			try {
+				reader.close();
+			} catch (IOException e) {
+				// ignore
+			}
+		}
+	}
+}
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptTestSuite.java b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptTestSuite.java
new file mode 100644
index 0000000..bbccc7d
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/JavaScriptTestSuite.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.e4.languages.javascript.junit;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import junit.framework.TestSuite;
+
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.ScriptableObject;
+
+public class JavaScriptTestSuite extends TestSuite {
+
+	private Collection scripts;
+	private ClassLoader applicationClassLoader;
+
+	public JavaScriptTestSuite(String testCaseName, Collection scripts) {
+		this(testCaseName, scripts, null);
+	}
+
+	public JavaScriptTestSuite(String testCaseName, Collection scripts, ClassLoader applicationClassLoader) {
+		super(testCaseName);
+		this.scripts = scripts;
+		this.applicationClassLoader = applicationClassLoader;
+
+		try {
+			findTests();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void findTests() throws Exception {
+		JavaScriptTestCase testCase = new JavaScriptTestCase(null, getName(), scripts, applicationClassLoader);
+		testCase.setUp();
+		try {
+			ArrayList testFunctionNames = new ArrayList();
+			ScriptableObject testCaseInstance = (ScriptableObject) testCase.createJavaScriptTestCaseInstance(null);
+			Object[] ids = ScriptableObject.getPropertyIds(testCaseInstance);
+			for (int i = 0; i < ids.length; i++) {
+				Object id = ids[i];
+				Object value = null;
+				if (id instanceof String) {
+					String fieldName = (String) id;
+					if (!fieldName.startsWith("test"))
+						continue;
+					value = ScriptableObject.getProperty(testCaseInstance, fieldName);
+					if (value instanceof BaseFunction)
+						testFunctionNames.add(fieldName);
+				}
+			}
+			Collections.sort(testFunctionNames);
+			for (Iterator iterator = testFunctionNames.iterator(); iterator.hasNext();) {
+				String testFunctionName = (String) iterator.next();
+				addTest(new JavaScriptTestCase(testFunctionName, getName(), scripts, applicationClassLoader));
+			}
+		} finally {
+			testCase.tearDown();
+		}
+	}
+}
diff --git a/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/assert.js b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/assert.js
new file mode 100644
index 0000000..5b3de07
--- /dev/null
+++ b/bundles/org.eclipse.e4.languages.javascript.junit/src/org/eclipse/e4/languages/javascript/junit/assert.js
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2010 IBM Corporation and others All rights reserved. This
+ * program and the accompanying materials are made available under the terms of
+ * the Eclipse Public License v1.0 which accompanies this distribution, and is
+ * available at http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+
+var Assert = {
+	assertTrue : function(message, asserted) {
+		if (arguments.length != 2) {
+			asserted = message;
+			message = null;
+		}
+		if (!asserted) {
+			Assert.fail(message ? message : 'assertTrue failed');
+		}
+	},
+	assertFalse : function(message, asserted) {
+		if (arguments.length != 2) {
+			asserted = message;
+			message = null;
+		}
+		if (asserted) {
+			Assert.fail(message ? message : 'assertFalse failed');
+		}
+	},
+	assertNull : function(message, asserted) {
+		if (arguments.length != 2) {
+			asserted = message;
+			message = null;
+		}
+		if (asserted !== null) {
+			Assert.fail(message ? message : 'assertNull failed: [' + asserted +']');
+		}
+	},
+	assertNotNull : function(message, asserted) {
+		if (arguments.length != 2) {
+			asserted = message;
+			message = null;
+		}
+		if (asserted === null) {
+			Assert.fail(message ? message : 'assertNotNull failed');
+		}
+	},
+	assertUndefined : function(message, asserted) {
+		if (arguments.length != 2) {
+			asserted = message;
+			message = null;
+		}
+		if (typeof asserted != 'undefined') {
+			Assert.fail(message ? message : 'assertUndefined failed: [' + asserted +']');
+		}
+	},
+	assertNotUndefined : function(message, asserted) {
+		if (arguments.length != 2) {
+			asserted = message;
+			message = null;
+		}
+		if (typeof asserted == 'undefined') {
+			Assert.fail(message ? message : 'assertNotUndefined failed');
+		}
+	},
+	assertSame : function(message, expected, value) {
+		if (arguments.length != 3) {
+			value = expected;
+			expected = message;
+			message = null;
+		}
+		if (expected === value)
+			return;
+		var expectedMessage = '- expected: [' + expected + '] but was [' + value + '].';
+		Assert.fail(message ? message : 'assertSame failed') + expectedMessage;
+	},
+	assertNotSame : function(message, expected, value) {
+		if (arguments.length != 3) {
+			value = expected;
+			expected = message;
+			message = null;
+		}
+		if (expected === value)
+			Assert.fail(message ? message : 'assertNotSame failed [' + value + '].');
+	},
+	assertEquals : function(message, expected, value) {
+		if (arguments.length != 3) {
+			value = expected;
+			expected = message;
+			message = null;
+		}
+		if (expected === value)
+			return;
+		if (expected !== null && typeof expected === 'object' && typeof expected.equals === 'function' && expected.equals(value))
+			return;
+		var expectedMessage = '- expected: [' + expected + '] but was [' + value + '].';
+		Assert.fail(message ? message : 'assertEquals failed') + expectedMessage;
+	},
+	assertNotEquals : function(message, expected, value) {
+		if (arguments.length != 3) {
+			value = expected;
+			expected = message;
+			message = null;
+		}
+		if (expected === value)
+			Assert.fail(message ? message : 'assertNotEquals failed [' + value + '].');
+		if (expected !== null && typeof expected === 'object' && typeof expected.equals === 'function' && expected.equals(value))
+			Assert.fail(message ? message : 'assertNotEquals failed [' + value + '].');
+	},
+	// For JSDT JUnit integration this method is over-ridden with a Java version
+	// with better stack support
+	fail : function(message) {
+		throw (message ? message : 'failed');
+	},
+	scopeAssert : function(scope) {
+		scope.fail = Assert.fail;
+		scope.assertNotEquals = Assert.assertNotEquals
+		scope.assertEquals = Assert.assertEquals;
+		scope.assertTrue = Assert.assertTrue;
+		scope.assertFalse = Assert.assertFalse;
+		scope.assertNotSame = Assert.assertNotSame;
+		scope.assertSame = Assert.assertSame;
+		scope.assertNull = Assert.assertNull;
+		scope.assertNotNull = Assert.assertNotNull;
+		scope.assertUndefined = Assert.assertUndefined;
+		scope.assertNotUndefined = Assert.assertNotUndefined;
+	}
+};
+Assert.scopeAssert(this);
+
+function TestCase(name) {
+	if (name)
+		this.setName(name);
+}
+TestCase.prototype = {
+	getName : function() {
+		return (this._testName) ? this._testName : null;
+	},
+	setName : function(name) {
+		this._testName = name;
+	},
+	getTestCaseName : function() {
+		return (this._testCaseName) ? this._testCaseName : null;
+	},
+	setTestCaseName : function(name) {
+		this._testCaseName = name;
+	},
+	setUp : function() {
+	},
+	tearDown : function() {
+	},
+	run : function() {
+		this.setUp();
+		try {
+			var testMethodName = this.getName();
+			if (!testMethodName) {
+				Assert.fail("Test method not set");
+			}
+			var testMethod = this[testMethodName];
+			if (!testMethod) {
+				Assert.fail("test method '" + testMethodName + "' not found in TestCase.");
+			}
+			if (!typeof testMethod === "function") {
+				Assert.fail("test method '" + testMethodName + "' is not a function.");
+			}
+			this[testMethodName]();
+		} finally {
+			this.tearDown();
+		}
+	}
+};
+TestCase.extend = function(testCaseName, protoObject) {
+	var F = function(testName) {
+		TestCase.call(this, testName);
+	};
+	var base = new TestCase();
+	base.setTestCaseName(testCaseName);
+	if (protoObject) {
+		for ( var prop in protoObject) {
+			base[prop] = protoObject[prop];
+		}
+	}
+	F.prototype = base;
+	return F;
+};