/*******************************************************************************
 * Copyright (c) 2009 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.vm2;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicReference;

import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.luaj.vm2.libs.ApproxLib;
import org.luaj.vm2.libs.jse.JsePlatform;
import org.luaj.vm2.libs.jse.LuaSqlMysqlLib;
import org.luaj.vm2.luajc.LuaJC;

/** 
 * Test compilation of various fragments that have
 * caused problems for jit compiling during development. 
 *
 */
public class FragmentsTest extends TestSuite {

	static final int TEST_TYPE_LUAC     = 0;
	static final int TEST_TYPE_LUAJC    = 1;

	public static class JseFragmentsTest extends FragmentsTestCase {
		public JseFragmentsTest() { super(  TEST_TYPE_LUAC ); }
	}
	public static class LuaJCFragmentsTest extends FragmentsTestCase {
		public LuaJCFragmentsTest() { super(  TEST_TYPE_LUAJC ); }
	}
	public static TestSuite suite() {
		TestSuite suite = new TestSuite("Compiler Fragments Tests");
		suite.addTest( new TestSuite( JseFragmentsTest.class,      "JSE Fragments Tests" ) );
		suite.addTest( new TestSuite( LuaJCFragmentsTest.class,    "LuaJC Fragments Tests" ) );
		return suite;
	}
	
	abstract protected static class  FragmentsTestCase extends TestCase {
	
		final int TEST_TYPE;

		protected FragmentsTestCase(int testType) {
			this.TEST_TYPE = testType;
		}
	
		public void runFragment( Varargs expected, String script ) {
			try {
				String name = getName();
				Globals globals = JsePlatform.debugGlobals();
				Reader reader = new StringReader(script);
				LuaValue chunk ;
				switch ( TEST_TYPE ) {
				case TEST_TYPE_LUAJC:
					LuaJC.install(globals);
					chunk = globals.load(reader, name);
					break;
				default:
					Prototype p = globals.compilePrototype(reader, name);
					chunk = new LuaClosure(p, globals);
					Print.print(p);
					break;
				}
				Varargs actual = chunk.invoke();
				assertEquals( expected.narg(), actual.narg() );
				for ( int i=1; i<=actual.narg(); i++ )
					assertEquals( expected.arg(i), actual.arg(i) );
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				fail(e.toString());
			}		
		}
		
		public void testFirstArgNilExtended() {
			runFragment( LuaValue.NIL, 
				"function f1(a) print( 'f1:', a ) return a end\n" +
				"b = f1()\n" + 
				"return b" );
		}

		public void testSimpleForloop() {
			runFragment( LuaValue.valueOf(77),
				"for n,p in ipairs({77}) do\n"+
				"	print('n,p',n,p)\n"+
				"   return p\n"+
				"end\n");
			
		}

		public void testFloorDivision() {
			runFragment(LuaValue.valueOf(2), "return 5//2\n");
		}

		public void testFloorDivisionNegativeRoundsDown() {
			runFragment(LuaValue.valueOf(-3), "return 5//-2\n");
		}

		public void testFloorDivisionInExpression() {
			runFragment(LuaValue.TRUE, "local x=5 local width=10 return x==width//2\n");
		}

		public void testContinueInLoops() {
			runFragment(
				LuaValue.valueOf(229),
				"local sum = 0\n" +
				"for i = 1, 5 do\n" +
				"  if i % 2 == 0 then continue end\n" +
				"  sum = sum + i\n" +
				"end\n" +
				"local j = 0\n" +
				"while j < 4 do\n" +
				"  j = j + 1\n" +
				"  if j < 3 then continue end\n" +
				"  sum = sum + 10\n" +
				"end\n" +
				"local k = 0\n" +
				"repeat\n" +
				"  k = k + 1\n" +
				"  if k < 3 then continue end\n" +
				"  sum = sum + 100\n" +
				"until k >= 4\n" +
				"return sum\n");
		}

		public void testDebugGetInfoLevelZeroReturnsGetInfoFunction() {
			runFragment(
				LuaValue.TRUE,
				"return debug.getinfo(0, 'f').func == debug.getinfo\n");
		}

		public void testDebugGetInfoInsideHookSeesHookFunction() {
			runFragment(
				LuaValue.TRUE,
				"local ok = false\n" +
				"local hook\n" +
				"hook = function()\n" +
				"  local info = debug.getinfo(1, 'f')\n" +
				"  ok = info ~= nil and info.func == hook\n" +
				"  debug.sethook()\n" +
				"end\n" +
				"debug.sethook(hook, 'c')\n" +
				"local function foo() end\n" +
				"foo()\n" +
				"return ok\n");
		}

		public void testInterruptedThreadStopsRunningClosure() throws Exception {
			final Globals globals = JsePlatform.debugGlobals();
			final LuaValue chunk = globals.load("while true do end", "interrupt.lua");
			final AtomicReference<Throwable> thrown = new AtomicReference<Throwable>();

			Thread t = new Thread(new Runnable() {
				public void run() {
					try {
						chunk.call();
					} catch (Throwable e) {
						thrown.set(e);
					}
				}
			}, "luaj-interrupt-test");
			t.start();

			Thread.sleep(50);
			t.interrupt();
			t.join(2000);

			assertFalse("lua thread should stop after interrupt", t.isAlive());
			assertNotNull("expected LuaError from interrupt", thrown.get());
			assertEquals(LuaError.class, thrown.get().getClass());
			assertTrue(thrown.get().getMessage().contains("interrupted"));
		}

		public void testBareExpressionReportsReadableToken() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				globals.load("1 + 1", "expr.lua");
				fail("expected syntax error");
			} catch (LuaError e) {
				assertTrue(e.getMessage().contains("unexpected symbol near 1"));
			}
		}

		public void testUnfinishedTableReportsEof() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				globals.load("mytab = {", "table.lua");
				fail("expected syntax error");
			} catch (LuaError e) {
				assertTrue(e.getMessage().contains("near <eof>"));
			}
		}

		public void testLongIntegerLiteralPrecision() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(9007199254740993L),
					LuaValue.valueOf(9007199254740994L),
					LuaValue.valueOf(9007199254740992L)
				}),
				"local x = 9007199254740993\nreturn x, x + 1, x - 1\n");
		}

		public void testBitwiseOperators() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(2),
					LuaValue.valueOf(7),
					LuaValue.valueOf(5),
					LuaValue.valueOf(12),
					LuaValue.valueOf(1),
					LuaValue.valueOf(-6)
				}),
				"return 6 & 3, 6 | 3, 6 ~ 3, 3 << 2, 6 >> 2, ~5\n");
		}

		public void testBitwisePrecedence() {
			runFragment(LuaValue.valueOf(3), "return 1 | 2 & 6\n");
		}

		public void testIoOpenReadModeDisallowsWrite() throws Exception {
			File file = writeTempFile("read-mode", "hello");
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs result = globals.load(
					"local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'r'))\n" +
					"return f:write('x')\n",
					"io_r.lua").invoke();
				assertTrue(result.arg1().isnil());
				assertTrue(result.arg(2).tojstring().indexOf("not writable") >= 0);
			} finally {
				file.delete();
			}
		}

		public void testIoOpenWriteModeDisallowsRead() throws Exception {
			File file = File.createTempFile("luaj-io", ".txt");
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs result = globals.load(
					"local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'w'))\n" +
					"return f:read('*a')\n",
					"io_w.lua").invoke();
				assertTrue(result.arg1().isnil());
				assertTrue(result.arg(2).tojstring().indexOf("not readable") >= 0);
			} finally {
				file.delete();
			}
		}

		public void testIoOpenUpdateModesSupportReadWrite() throws Exception {
			File file = writeTempFile("update-mode", "abc");
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs result = globals.load(
					"local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'r+'))\n" +
					"local before = f:read('*a')\n" +
					"f:seek('set', 0)\n" +
					"f:write('xyz')\n" +
					"f:seek('set', 0)\n" +
					"local after = f:read('*a')\n" +
					"f:close()\n" +
					"return before, after\n",
					"io_rplus.lua").invoke();
				assertEquals("abc", result.arg1().tojstring());
				assertEquals("xyz", result.arg(2).tojstring());
			} finally {
				file.delete();
			}
		}

		public void testIoOpenAppendPreservesExistingContent() throws Exception {
			File file = writeTempFile("append-mode", "abc");
			try {
				Globals globals = JsePlatform.standardGlobals();
				LuaValue result = globals.load(
					"local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'a'))\n" +
					"f:write('xyz')\n" +
					"f:close()\n" +
					"local g = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'r'))\n" +
					"local contents = g:read('*a')\n" +
					"g:close()\n" +
					"return contents\n",
					"io_a.lua").call();
				assertEquals("abcxyz", result.tojstring());
			} finally {
				file.delete();
			}
		}

		public void testIoOpenAppendUpdateAppendsOnWriteAfterSeek() throws Exception {
			File file = writeTempFile("append-update", "abc");
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs result = globals.load(
					"local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'a+'))\n" +
					"f:seek('set', 0)\n" +
					"local before = f:read('*a')\n" +
					"f:seek('set', 0)\n" +
					"f:write('xyz')\n" +
					"f:seek('set', 0)\n" +
					"local after = f:read('*a')\n" +
					"f:close()\n" +
					"return before, after\n",
					"io_aplus.lua").invoke();
				assertEquals("abc", result.arg1().tojstring());
				assertEquals("abcxyz", result.arg(2).tojstring());
			} finally {
				file.delete();
			}
		}

		public void testPairsUsesMetatableOverride() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf("k"),
					LuaValue.valueOf("v")
				}),
				"local t = setmetatable({}, {\n" +
				"  __pairs = function(self)\n" +
				"    local done = false\n" +
				"    return function()\n" +
				"      if done then return nil end\n" +
				"      done = true\n" +
				"      return 'k', 'v'\n" +
				"    end, self, nil\n" +
				"  end\n" +
				"})\n" +
				"for k, v in pairs(t) do\n" +
				"  return k, v\n" +
				"end\n");
		}

		public void testIpairsUsesMetatableOverride() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(7),
					LuaValue.valueOf("x")
				}),
				"local t = setmetatable({}, {\n" +
				"  __ipairs = function(self)\n" +
				"    local done = false\n" +
				"    return function()\n" +
				"      if done then return nil end\n" +
				"      done = true\n" +
				"      return 7, 'x'\n" +
				"    end, self, 0\n" +
				"  end\n" +
				"})\n" +
				"for i, v in ipairs(t) do\n" +
				"  return i, v\n" +
				"end\n");
		}

		public void testApproxEqLibrary() {
			try {
				Globals globals = JsePlatform.standardGlobals();
				globals.load(new ApproxLib());
				Varargs result = globals.load(
					"return approx_eq(1.0, 0.9999999995, 1e-8), " +
					"approx.eq(1.0, 0.9, 1e-8)\n",
					"approx.lua").invoke();
				assertEquals(LuaValue.TRUE, result.arg1());
				assertEquals(LuaValue.FALSE, result.arg(2));
			} catch (Exception e) {
				e.printStackTrace();
				fail(e.toString());
			}
		}

		public void testCjsonRequireEncodeDecode() {
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs result = globals.load(
					"local cjson = require('cjson')\n" +
					"local json = cjson.encode({name='lua', values={1, true, cjson.null}})\n" +
					"local decoded = cjson.decode(json)\n" +
					"return decoded.name, decoded.values[1], decoded.values[2], decoded.values[3] == cjson.null\n",
					"cjson.lua").invoke();
				assertEquals(LuaValue.valueOf("lua"), result.arg1());
				assertEquals(LuaValue.valueOf(1), result.arg(2));
				assertEquals(LuaValue.TRUE, result.arg(3));
				assertEquals(LuaValue.TRUE, result.arg(4));
			} catch (Exception e) {
				e.printStackTrace();
				fail(e.toString());
			}
		}

		public void testLuaSqlMysqlRequireAndExecute() throws Exception {
			LuaSqlMysqlLib.FakeMysqlDriver driver = new LuaSqlMysqlLib.FakeMysqlDriver();
			DriverManager.registerDriver(driver);
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs result = globals.load(
					"local luasql = require('luasql.mysql')\n" +
					"local env = luasql.mysql('org.luaj.vm2.libs.jse.LuaSqlMysqlLib$FakeMysqlDriver')\n" +
					"local conn = assert(env:connect('demo', 'u', 'p', 'localhost', 3306))\n" +
					"local cur = assert(conn:execute(\"select 1 as answer, 'ok' as status\"))\n" +
					"local row = assert(cur:fetch({}, 'a'))\n" +
					"cur:close()\n" +
					"conn:close()\n" +
					"env:close()\n" +
					"return row.answer, row.status\n",
					"luasql_mysql.lua").invoke();
				assertEquals(LuaValue.valueOf(1), result.arg1());
				assertEquals(LuaValue.valueOf("ok"), result.arg(2));
			} finally {
				try {
					DriverManager.deregisterDriver(driver);
				} catch (SQLException ignore) {
				}
			}
		}

		public void testIoLinesClosesImplicitFileAtEnd() throws Exception {
			File file = writeTempFile("lines-close", "a\nb\n");
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs iterTriplet = globals.get("io").get("lines").invoke(LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(file.getAbsolutePath())
				}));
				LuaValue iter = iterTriplet.arg1();
				assertEquals(LuaValue.valueOf("a"), iter.call());
				assertEquals(LuaValue.valueOf("b"), iter.call());
				assertEquals(LuaValue.NIL, iter.call());

				Field f = iter.getClass().getDeclaredField("f");
				f.setAccessible(true);
				Object fileHandle = f.get(iter);
				Method isclosed = fileHandle.getClass().getDeclaredMethod("isclosed");
				isclosed.setAccessible(true);
				assertEquals(Boolean.TRUE, isclosed.invoke(fileHandle));
			} finally {
				file.delete();
			}
		}

		public void testIoLinesxCanBeClosedEarly() throws Exception {
			File file = writeTempFile("linesx-close", "a\nb\n");
			try {
				Globals globals = JsePlatform.standardGlobals();
				Varargs result = globals.load(
					"local it = io.linesx(" + quote(file.getAbsolutePath()) + ")\n" +
					"local first = it()\n" +
					"it:close()\n" +
					"return first, it()\n",
					"linesx.lua").invoke();
				assertEquals(LuaValue.valueOf("a"), result.arg1());
				assertEquals(LuaValue.NIL, result.arg(2));
			} finally {
				file.delete();
			}
		}

		public void testTableMove() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(1),
					LuaValue.valueOf(2),
					LuaValue.valueOf(3),
					LuaValue.valueOf(1),
					LuaValue.valueOf(2),
					LuaValue.valueOf(3)
				}),
				"local t = {1,2,3}\nlocal d = {}\ntable.move(t, 1, 3, 1, d)\nreturn d[1], d[2], d[3], table.move(t, 1, 3, 2)[2], t[3], t[4]\n");
		}

		public void testUtf8Library() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(2),
					LuaValue.valueOf(228),
					LuaValue.valueOf(97),
					LuaValue.valueOf(2),
					LuaValue.valueOf(3)
				}),
				"local s = utf8.char(97, 228)\nlocal iter, state, var = utf8.codes(s)\nlocal _, cp = iter(state, var)\nreturn utf8.len(s), utf8.codepoint(s, 2), cp, utf8.offset(s, 2)\n");
		}

		public void testStringPackUnpack() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(-5),
					LuaValue.valueOf(513),
					LuaValue.valueOf("hi"),
					LuaValue.valueOf(10),
					LuaValue.valueOf(7)
				}),
				"local s = string.pack('<i4I2z', -5, 513, 'hi')\nlocal a,b,c,n = string.unpack('<i4I2z', s)\nreturn a,b,c,n,string.packsize('<i4I2x')\n");
		}

		public void testStringPackFixedString() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf("ab\000\000"),
					LuaValue.valueOf("ab\000\000"),
					LuaValue.valueOf(5)
				}),
				"local s = string.pack('>c4', 'ab')\nreturn s, (string.unpack('>c4', s)), select(2, string.unpack('>c4', s))\n");
		}

		public void testUnicodeEscapeLiteral53() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(228),
					LuaValue.valueOf(128578),
					LuaValue.valueOf(6)
				}),
				"local s = '\\u{E4}\\u{1F642}'\nreturn utf8.codepoint(s, 1), utf8.codepoint(s, 3), #s\n");
		}

		public void testUnicodeEscapeLiteralRejectsInvalidCodepoint() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				globals.load("return '\\u{110000}'", getName());
				fail("expected LuaError");
			} catch (LuaError e) {
				assertTrue(e.getMessage().indexOf("UTF-8 value too large") >= 0);
			}
		}

		public void testStringDumpRoundTrip53() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(123),
					LuaValue.valueOf(85)
				}),
				"local dumped = string.dump(function() return 123 end)\n" +
				"local loaded = assert(load(dumped, 'dumped', 'b'))\n" +
				"return loaded(), string.byte(dumped, 5)\n");
		}

		public void testMath53Helpers() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf("integer"),
					LuaValue.valueOf(3),
					LuaValue.FALSE,
					LuaValue.valueOf(Long.MAX_VALUE),
					LuaValue.valueOf(Long.MIN_VALUE),
					LuaValue.valueOf("Lua 5.5")
				}),
				"return math.type(3), math.tointeger(3.0), math.ult(-1, 1), math.maxinteger, math.mininteger, _VERSION\n");
		}

		public void testTableCreate55() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(3),
					LuaValue.valueOf("x"),
					LuaValue.valueOf("x")
				}),
				"local t = table.create(3, 'x')\nreturn #t, t[1], t[3]\n");
		}

		public void testHexFloatLiteralAndTonumber() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(3.0),
					LuaValue.valueOf(3.0),
					LuaValue.valueOf(16)
				}),
				"return 0x1.8p1, tonumber('0x1.8p1'), tonumber('0x10')\n");
		}

		public void testCoroutineIsYieldable() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.FALSE,
					LuaValue.TRUE
				}),
				"local co = coroutine.create(function() return coroutine.isyieldable() end)\n" +
				"local ok, value = coroutine.resume(co)\n" +
				"return coroutine.isyieldable(), value\n");
		}

		public void testCoroutineRunningReturnsThreadAndMainFlag() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.TRUE,
					LuaValue.TRUE
				}),
				"local mainThread, isMain = coroutine.running()\n" +
				"local co = coroutine.create(function()\n" +
				"  local running, childMain = coroutine.running()\n" +
				"  return type(running) == 'thread', childMain == false\n" +
				"end)\n" +
				"local ok, childHasThread, childIsNotMain = coroutine.resume(co)\n" +
				"return isMain and type(mainThread) == 'thread', ok and childHasThread and childIsNotMain\n");
		}

		public void testMathRandomSupportsLongBounds() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf("integer"),
					LuaValue.TRUE
				}),
				"math.randomseed(123)\nlocal v = math.random(9007199254740993, 9007199254740995)\nreturn math.type(v), v >= 9007199254740993 and v <= 9007199254740995\n");
		}

		public void testMathRandomseed54ReturnsSeedsAndReseedsDeterministically() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(11),
					LuaValue.valueOf(22),
					LuaValue.TRUE
				}),
				"local a, b = math.randomseed(11, 22)\n" +
				"local first = { math.random(), math.random(1, 1000000) }\n" +
				"math.randomseed(11, 22)\n" +
				"local second = { math.random(), math.random(1, 1000000) }\n" +
				"return a, b, first[1] == second[1] and first[2] == second[2]\n");
		}

		public void testMathLogWithBase53() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(3.0),
					LuaValue.valueOf(3.0)
				}),
				"return math.log(8, 2), math.log(27, 3)\n");
		}

		public void testToBeClosedVariableRunsCloseOnScopeExit() {
			runFragment(LuaValue.valueOf("closed"),
				"local mt = { __close = function(self, err) _G.marker = err or 'closed' end }\n" +
				"do\n" +
				"  local h <close> = setmetatable({}, mt)\n" +
				"end\n" +
				"return marker\n");
		}

		public void testToBeClosedVariableRunsCloseOnError() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.FALSE,
					LuaValue.TRUE,
					LuaValue.TRUE
				}),
				"local mt = { __close = function(self, err) _G.marker = err end }\n" +
				"local ok, err = pcall(function()\n" +
				"  local h <close> = setmetatable({}, mt)\n" +
				"  error('boom')\n" +
				"end)\n" +
				"return ok, string.find(err, 'boom', 1, true) ~= nil, string.find(marker, 'boom', 1, true) ~= nil\n");
		}

		public void testToBeClosedVariableClosesEachReusedSlot() {
			runFragment(LuaValue.valueOf("xx"),
				"local mt = { __close = function(self, err) _G.marker = (_G.marker or '') .. 'x' end }\n" +
				"do local a <close> = setmetatable({}, mt) end\n" +
				"do local b <close> = setmetatable({}, mt) end\n" +
				"return marker\n");
		}

		public void testToBeClosedVariableRejectsNil() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.FALSE,
					LuaValue.TRUE
				}),
				"local ok, err = pcall(function()\n" +
				"  local x <close> = nil\n" +
				"end)\n" +
				"return ok, string.find(err, 'non-closable', 1, true) ~= nil\n");
		}

		public void testToBeClosedVariableRejectsMissingMetamethod() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.FALSE,
					LuaValue.TRUE
				}),
				"local ok, err = pcall(function()\n" +
				"  local x <close> = {}\n" +
				"end)\n" +
				"return ok, string.find(err, 'non-closable', 1, true) ~= nil\n");
		}

		public void testConstLocalRejectsAssignment() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				switch (TEST_TYPE) {
				case TEST_TYPE_LUAJC:
					LuaJC.install(globals);
					globals.load(new StringReader("local x <const> = 1\nx = 2\nreturn x\n"), getName());
					break;
				default:
					globals.compilePrototype(new StringReader("local x <const> = 1\nx = 2\nreturn x\n"), getName());
					break;
				}
				fail("expected LuaError");
			} catch (LuaError e) {
				assertTrue(e.getMessage().indexOf("attempt to assign to const variable") >= 0);
			} catch (Exception e) {
				fail(e.toString());
			}
		}

		public void testConstUpvalueRejectsAssignment() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				switch (TEST_TYPE) {
				case TEST_TYPE_LUAJC:
					LuaJC.install(globals);
					globals.load(new StringReader("local x <const> = 1\nreturn function() x = 2 end\n"), getName());
					break;
				default:
					globals.compilePrototype(new StringReader("local x <const> = 1\nreturn function() x = 2 end\n"), getName());
					break;
				}
				fail("expected LuaError");
			} catch (LuaError e) {
				assertTrue(e.getMessage().indexOf("attempt to assign to const variable") >= 0);
			} catch (Exception e) {
				fail(e.toString());
			}
		}

		public void testGlobalDeclaration55() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(7),
					LuaValue.valueOf(7)
				}),
				"global answer = 7\nlocal function f() return answer end\nreturn f(), answer\n");
		}

		public void testGlobalDeclarationRejectsUndeclaredName() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				switch (TEST_TYPE) {
				case TEST_TYPE_LUAJC:
					LuaJC.install(globals);
					globals.load(new StringReader("global answer = 7\nreturn missing\n"), getName());
					break;
				default:
					globals.compilePrototype(new StringReader("global answer = 7\nreturn missing\n"), getName());
					break;
				}
				fail("expected LuaError");
			} catch (LuaError e) {
				assertTrue(e.getMessage().indexOf("is not declared") >= 0);
			} catch (Exception e) {
				fail(e.toString());
			}
		}

		public void testGlobalConstRejectsAssignment55() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				switch (TEST_TYPE) {
				case TEST_TYPE_LUAJC:
					LuaJC.install(globals);
					globals.load(new StringReader("global <const> answer = 7\nanswer = 8\n"), getName());
					break;
				default:
					globals.compilePrototype(new StringReader("global <const> answer = 7\nanswer = 8\n"), getName());
					break;
				}
				fail("expected LuaError");
			} catch (LuaError e) {
				assertTrue(e.getMessage().indexOf("attempt to assign to const variable") >= 0);
			} catch (Exception e) {
				fail(e.toString());
			}
		}

		public void testNamedVarargTable55() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(1),
					LuaValue.valueOf(2),
					LuaValue.valueOf(2),
					LuaValue.valueOf(3),
					LuaValue.valueOf(2)
				}),
				"local function f(a, ...rest)\n" +
				"  return a, rest[1], rest.n, rest[2], select('#', ...)\n" +
				"end\n" +
				"return f(1, 2, 3)\n");
		}

		public void testNamedVarargDumpRoundTrip55() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.valueOf(2),
					LuaValue.valueOf(5)
				}),
				"local dumped = string.dump(function(...rest) return rest.n, rest[2] end)\n" +
				"local loaded = assert(load(dumped, 'dumped', 'b'))\n" +
				"return loaded(4, 5)\n");
		}

		public void testForLoopVariableRejectsAssignment55() {
			Globals globals = JsePlatform.debugGlobals();
			try {
				switch (TEST_TYPE) {
				case TEST_TYPE_LUAJC:
					LuaJC.install(globals);
					globals.load(new StringReader("for i = 1, 3 do i = 7 end\n"), getName());
					break;
				default:
					globals.compilePrototype(new StringReader("for i = 1, 3 do i = 7 end\n"), getName());
					break;
				}
				fail("expected LuaError");
			} catch (LuaError e) {
				assertTrue(e.getMessage().indexOf("attempt to assign to const variable") >= 0);
			} catch (Exception e) {
				fail(e.toString());
			}
		}

		public void testCoroutineClose54() {
			runFragment(
				LuaValue.varargsOf(new LuaValue[] {
					LuaValue.TRUE,
					LuaValue.valueOf("dead"),
					LuaValue.FALSE
				}),
				"local co = coroutine.create(function() coroutine.yield('pause') end)\n" +
				"coroutine.resume(co)\n" +
				"local ok = coroutine.close(co)\n" +
				"local resumed = coroutine.resume(co)\n" +
				"return ok, coroutine.status(co), resumed\n");
		}

		public void testStringDumpRoundTrip54ToClose() {
			runFragment(
				LuaValue.valueOf("closed"),
				"local dumped = string.dump(function()\n" +
				"  local mt = { __close = function(self, err) _G.marker = err or 'closed' end }\n" +
				"  do local h <close> = setmetatable({}, mt) end\n" +
				"  return marker\n" +
				"end)\n" +
				"local loaded = assert(load(dumped, 'dumped', 'b'))\n" +
				"return loaded()\n");
		}

		public void testLuaJCSupportsCloseLocals() {
			if (TEST_TYPE != TEST_TYPE_LUAJC)
				return;
			try {
				Globals globals = JsePlatform.debugGlobals();
				LuaJC.install(globals);
				LuaValue chunk = globals.load(new StringReader(
					"local mt = { __close = function(self, err) _G.marker = err or 'closed' end }\n" +
					"do local h <close> = setmetatable({}, mt) end\n" +
					"return marker\n"), getName());
				assertEquals(LuaValue.valueOf("closed"), chunk.call());
				assertFalse(chunk instanceof LuaClosure);
			} catch (Exception e) {
				fail(e.toString());
			}
		}

		public void testStandardGlobalsDoNotExposeBit32() {
			runFragment(LuaValue.TRUE, "return bit32 == nil\n");
		}
			
		public void testForloopParamUpvalues() {
			runFragment( LuaValue.varargsOf(new LuaValue[] { 
					LuaValue.valueOf(77), 
					LuaValue.valueOf(1) } ),
				"for n,p in ipairs({77}) do\n"+
				"	print('n,p',n,p)\n"+
				"	foo = function()\n"+
				"		return p,n\n"+
				"	end\n"+
				"	return foo()\n"+
				"end\n");
			
		}
			
		public void testArgVarargsUseBoth() {
			runFragment( LuaValue.varargsOf( new LuaValue[] { 
					LuaValue.valueOf("a"), 
					LuaValue.valueOf("b"), 
					LuaValue.valueOf("c")}),
				"function v(arg,...)\n" +
				"	return arg,...\n" +
				"end\n" +
				"return v('a','b','c')\n" );
		}
		
		public void testArgParamUseNone() {
			runFragment( LuaValue.valueOf("string"), 
				"function v(arg,...)\n" +
				"	return type(arg)\n" +
				"end\n" +
				"return v('abc')\n" );		
		}

		public void testSetlistVarargs() {
			runFragment( LuaValue.valueOf("abc"),
				"local f = function() return 'abc' end\n" +
				"local g = { f() }\n" +
				"return g[1]\n" );
		}
		
		public void testSelfOp() {
			runFragment( LuaValue.valueOf("bcd"), 
				"local s = 'abcde'\n"+
				"return s:sub(2,4)\n" );		
		}
		
		public void testSetListWithOffsetAndVarargs() {
			runFragment( LuaValue.valueOf(1003), 
				"local bar = {1000, math.sqrt(9)}\n"+
				"return bar[1]+bar[2]\n" );
		}
		
		public void testMultiAssign() {
			// arargs evaluations are all done before assignments 
			runFragment( LuaValue.varargsOf(new LuaValue[]{
					LuaValue.valueOf(111),
					LuaValue.valueOf(111),
					LuaValue.valueOf(111)}), 
				"a,b,c = 1,10,100\n" +
				"a,b,c = a+b+c, a+b+c, a+b+c\n" +
				"return a,b,c\n" );		
		}
	
		public void testUpvalues() {
			runFragment( LuaValue.valueOf(999), 
				"local a = function(x)\n" +
				"  return function(y)\n" +
				"    return x + y\n" +
				"  end\n" +
				"end\n" +
				"local b = a(222)\n" +
				"local c = b(777)\n" +
				"print( 'c=', c )\n" +
				"return c\n" );		
		}
				
		public void testNonAsciiStringLiterals() {
			runFragment( LuaValue.valueOf("7,8,12,10,9,11,133,222"), 
				"local a='\\a\\b\\f\\n\\t\\v\\133\\222'\n"+
				"local t={string.byte(a,1,#a)}\n"+
				"return table.concat(t,',')\n" );
		}
	
		public void testControlCharStringLiterals() {
			runFragment( LuaValue.valueOf("97,0,98,18,99,18,100,18,48,101"), 
				"local a='a\\0b\\18c\\018d\\0180e'\n"+
				"local t={string.byte(a,1,#a)}\n"+
				"return table.concat(t,',')\n" );		
		}
		
		public void testLoopVarNames() {
			runFragment( LuaValue.valueOf(" 234,1,aa 234,2,bb"), 
				"local w = ''\n"+
				"function t()\n"+
				"	for f,var in ipairs({'aa','bb'}) do\n"+
				"		local s = 234\n"+
				"		w = w..' '..s..','..f..','..var\n"+
				"	end\n"+
				"end\n" +
				"t()\n" +
				"return w\n" );
			
		}
		
		public void testForLoops() {
			runFragment( LuaValue.valueOf("12345 357 963"), 
				"local s,t,u = '','',''\n"+
				"for m=1,5 do\n"+
				"	s = s..m\n"+
				"end\n"+
				"for m=3,7,2 do\n"+
				"	t = t..m\n"+
				"end\n"+
				"for m=9,3,-3 do\n"+
				"	u = u..m\n"+
				"end\n"+
				"return s..' '..t..' '..u\n" );
		}
		
		public void testLocalFunctionDeclarations() {
			runFragment( LuaValue.varargsOf(LuaValue.valueOf("function"),LuaValue.valueOf("nil")), 
				"local function aaa()\n"+
				"	return type(aaa)\n"+
				"end\n"+
				"local bbb = function()\n"+
				"	return type(bbb)\n"+
				"end\n"+
				"return aaa(),bbb()\n" );
		}
		
		public void testNilsInTableConstructor() {
			runFragment( LuaValue.valueOf("1=111 2=222 3=333 "), 
				"local t = { 111, 222, 333, nil, nil }\n"+
				"local s = ''\n"+
				"for i,v in ipairs(t) do \n" +
				"	s=s..tostring(i)..'='..tostring(v)..' '\n" +
				"end\n"+
				"return s\n" );
			
		}
		
		public void testUnreachableCode() {
			runFragment( LuaValue.valueOf(66), 
				"local function foo(x) return x * 2 end\n" + 
				"local function bar(x, y)\n" + 
				"	if x==y then\n" + 
				"		return y\n" + 
				"	else\n" + 
				"		return foo(x)\n" + 
				"	end\n" + 
				"end\n" + 
				"return bar(33,44)\n" ); 
			
		}
		public void testVarargsWithParameters() {
			runFragment( LuaValue.valueOf(222), 
				"local func = function(t,...)\n"+ 
				"	return (...)\n"+
				"end\n"+
				"return func(111,222,333)\n" );
		}
		
		public void testNoReturnValuesPlainCall() {
			runFragment( LuaValue.TRUE, 
				"local testtable = {}\n"+
				"return pcall( function() testtable[1]=2 end )\n" );
		}
			
		public void testVarargsInTableConstructor() {
			runFragment( LuaValue.valueOf(222), 
				"local function foo() return 111,222,333 end\n"+
				"local t = {'a','b',c='c',foo()}\n"+
				"return t[4]\n" );
		}
	
		public void testVarargsInFirstArg() {
			runFragment( LuaValue.valueOf(123), 
					"function aaa(x) return x end\n" +
					"function bbb(y) return y end\n" +
					"function ccc(z) return z end\n" +
					"return ccc( aaa(bbb(123)), aaa(456) )\n" );
		}
		
		public void testSetUpvalueTableInitializer() {
			runFragment( LuaValue.valueOf("b"), 
					"local aliases = {a='b'}\n" +
					"local foo = function()\n" +
					"	return aliases\n" +
					"end\n" +
					"return foo().a\n"  );
		}
		
		
		public void testLoadNilUpvalue() {
			runFragment( LuaValue.NIL, 
					"tostring = function() end\n" +
					"local pc \n" +
					"local pcall = function(...)\n" +
					"	pc(...)\n" +
					"end\n" +
					"return NIL\n" );
		}
		
		public void testUpvalueClosure() {
			runFragment( LuaValue.NIL, 
					"print()\n"+
					"local function f2() end\n"+
					"local function f3()\n"+
					"	return f3\n"+
					"end\n" +
					"return NIL\n" );
		}
	
		public void testUninitializedUpvalue() {
			runFragment( LuaValue.NIL, 
					"local f\n"+	
					"do\n"+
					"	function g()\n"+
					"		print(f())\n"+
					"	end\n"+
					"end\n" +
					"return NIL\n" );
		}
	
		public void testTestOpUpvalues() {
			runFragment( LuaValue.varargsOf(LuaValue.valueOf(1),LuaValue.valueOf(2),LuaValue.valueOf(3)), 
					"print( nil and 'T' or 'F' )\n"+
					"local a,b,c = 1,2,3\n"+
					"function foo()\n"+
					"	return a,b,c\n"+
					"end\n" +
					"return foo()\n" );				
		}
		public void testTestSimpleBinops() {
			runFragment( LuaValue.varargsOf(new LuaValue[] {
					LuaValue.FALSE, LuaValue.FALSE, LuaValue.TRUE, LuaValue.TRUE, LuaValue.FALSE }),
					"local a,b,c = 2,-2.5,0\n" +
					"return (a==c), (b==c), (a==a), (a>c), (b>0)\n" );
		}
		
		public void testNumericForUpvalues() {
			runFragment( LuaValue.valueOf(8), 
					"for i = 3,4 do\n"+
					"	local j = i + 5\n"+
					"	local a = function()\n"+
					"		return j\n"+
					"	end\n" +
					"	return a()\n"+
					"end\n");
		}
		
		public void testNumericForUpvalues2() {
			runFragment( LuaValue.valueOf("222 222"), 
					"local t = {}\n"+
					"local template = [[123 456]]\n"+
					"for i = 1,2 do\n"+
					"	t[i] = template:gsub('%d', function(s)\n"+
					"		return i\n"+
					"	end)\n"+
					"end\n" +
					"return t[2]\n");
		}
		
		public void testReturnUpvalue() {
			runFragment( LuaValue.varargsOf(new LuaValue[] { LuaValue.ONE, LuaValue.valueOf(5), }),
					"local a = 1\n"+
					"local b\n"+
					"function c()\n"+
					"	b=5\n" +
					"	return a\n"+
					"end\n"+
					"return c(),b\n" );			
		}
		
		public void testUninitializedAroundBranch() {
			runFragment( LuaValue.valueOf(333), 
					"local state\n"+
					"if _G then\n"+
					"    state = 333\n"+
					"end\n"+
					"return state\n" );
		}
		
		public void testLoadedNilUpvalue() {
			runFragment( LuaValue.NIL, 
					"local a = print()\n"+   
					"local b = c and { d = e }\n"+
					"local f\n"+
					"local function g()\n"+
					"	return f\n"+
					"end\n" +
					"return g()\n" );
		}
		
		public void testUpvalueInFirstSlot() {
			runFragment( LuaValue.valueOf("foo"), 
					"local p = {'foo'}\n"+
					"bar = function()\n"+
					"	return p \n"+
					"end\n"+
					"for i,key in ipairs(p) do\n"+
					"	print()\n"+
					"end\n" +
					"return bar()[1]");
		}
		
		public void testReadOnlyAndReadWriteUpvalues() {
			runFragment( LuaValue.varargsOf( new LuaValue[] { LuaValue.valueOf(333), LuaValue.valueOf(222) } ),
					"local a = 111\n" +
					"local b = 222\n" +
					"local c = function()\n"+
					"	a = a + b\n" +
					"	return a,b\n"+
					"end\n" +
					"return c()\n" );
		}
		
		public void testNestedUpvalues() {
			runFragment( LuaValue.varargsOf( new LuaValue[] { LuaValue.valueOf(5), LuaValue.valueOf(8), LuaValue.valueOf(9) } ),
					"local x = 3\n"+
					"local y = 5\n"+	   
					"local function f()\n"+
					"   return y\n"+
					"end\n"+
					"local function g(x1, y1)\n"+
					"   x = x1\n"+
					"   y = y1\n" +
					"	return x,y\n"+
					"end\n"+
					"return f(), g(8,9)\n"+
					"\n" );
		}
		
		public void testLoadBool() {
			runFragment( LuaValue.NONE, 
					"print( type(foo)=='string' )\n"+
					"local a,b\n"+
					"if print() then\n"+
					"	b = function()\n"+
					"		return a\n"+
					"	end\n"+
					"end\n" );
		}
		
		public void testBasicForLoop() {
			runFragment( LuaValue.valueOf(2), 
					"local data\n"+
					"for i = 1, 2 do\n"+
					"     data = i\n"+
					"end\n"+
					"local bar = function()\n"+
					"	return data\n"+
					"end\n" +
					"return bar()\n" );
		}
		
		public void testGenericForMultipleValues() {
			runFragment( LuaValue.varargsOf(LuaValue.valueOf(3),LuaValue.valueOf(2),LuaValue.valueOf(1)), 
					"local iter = function() return 1,2,3,4 end\n" +
					"local foo  = function() return iter,5 end\n" +
			        "for a,b,c in foo() do\n" +
			        "    return c,b,a\n" +
			        "end\n" );
		}
		
		public void testPhiUpvalue() {
			runFragment( LuaValue.valueOf(6), 
					"local a = foo or 0\n"+
					"local function b(c)\n"+
					"	if c > a then a = c end\n" +
					"	return a\n"+
					"end\n" +
					"b(6)\n" +
					"return a\n" );
		}
		
		public void testAssignReferUpvalues() {
			runFragment( LuaValue.valueOf(123), 
					"local entity = 234\n" +
				    "local function c()\n" +
					"    return entity\n" +
					"end\n" +
				    "entity = (a == b) and 123\n" +
				    "if entity then\n" +
				    "    return entity\n" +
				    "end\n" );			
		}
		
		public void testSimpleRepeatUntil() {
			runFragment( LuaValue.valueOf(5), 
					"local a\n"+
					"local w\n"+
					"repeat\n"+
					"	a = w\n"+
					"until not a\n" +
					"return 5\n" );
		}
		
		public void testLoopVarUpvalues() {
			runFragment( LuaValue.valueOf("b"), 
					"local env = {}\n" +
					"for a,b in pairs(_G) do\n" +
					"	c = function()\n" +
					"		return b\n" +
					"	end\n" +
					"end\n" +
					"local e = env\n" +
					"local f = {a='b'}\n" +
					"for k,v in pairs(f) do\n" +
					"	return env[k] or v\n" +
					"end\n");
		}
		
		public void testPhiVarUpvalue() {
			runFragment( LuaValue.valueOf(2), 
					"local a = 1\n"+    
				    "local function b()\n"+
				    "    a = a + 1\n"+
				    "    return function() end\n"+
				    "end\n"+
				    "for i in b() do\n"+
					"	a = 3\n"+
				    "end\n" +
				    "return a\n");			
		}
		
		public void testUpvaluesInElseClauses() {
			runFragment( LuaValue.valueOf(111), 
					"if a then\n" +
			        "   foo(bar)\n" +
			        "elseif _G then\n" +
			        "    local x = 111\n" +
			        "    if d then\n" +
			        "        foo(bar)\n" +
			        "    else\n" +
			        "    	local y = function()\n" +
			        "    		return x\n" +
			        "        end\n" +
			        "    	return y()\n" +
			        "    end\n" +
			        "end\n");
		}
		
		public void testUpvalueInDoBlock() {
			runFragment( LuaValue.NONE, "do\n"+
					"	local x = 10\n"+
					"	function g()\n"+
					"		return x\n"+
					"	end\n"+
					"end\n"+
					"g()\n");
		}
		
		public void testNullError() {
			runFragment( LuaValue.varargsOf(LuaValue.FALSE, LuaValue.NIL),
					"return pcall(error)\n");
		}

		public void testFindWithOffset() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf(8), LuaValue.valueOf(5)),
					"string = \"abcdef:ghi\"\n" +
					"substring = string:sub(3)\n" +
					"idx = substring:find(\":\")\n" +
					"return #substring, idx\n");
		}

		public void testErrorArgIsString() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf("string"), LuaValue.valueOf("c")),
					"a,b = pcall(error, 'c'); return type(b), b\n");
		}
		public void testErrorArgIsNil() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf("nil"), LuaValue.NIL),
					"a,b = pcall(error); return type(b), b\n");
		}
		public void testErrorArgIsTable() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf("table"), LuaValue.valueOf("d")),
					"a,b = pcall(error, {c='d'}); return type(b), b.c\n");
		}
		public void testErrorArgIsNumber() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf("string"), LuaValue.valueOf("1")),
					"a,b = pcall(error, 1); return type(b), b\n");
		}
		public void testErrorArgIsBool() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf("boolean"), LuaValue.TRUE),
					"a,b = pcall(error, true); return type(b), b\n");
		}
		public void testXpcallHandlerResultReplacesStringError() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf("table"), LuaValue.valueOf(1)),
					"local ok, err = xpcall(function() error('oh no') end, function(e) return { marker = 1, original = e } end)\n" +
					"return type(err), err.marker\n");
		}
		public void testXpcallHandlerGetsOriginalErrorObject() {
			runFragment(LuaValue.varargsOf(LuaValue.valueOf("table"), LuaValue.valueOf(1)),
					"local source = { marker = 1 }\n" +
					"local ok, err = xpcall(function() error(source) end, function(e) return e end)\n" +
					"return type(err), err.marker\n");
		}
		public void testBalancedMatchOnEmptyString() {
			runFragment(LuaValue.NIL, "return (\"\"):match(\"%b''\")\n");
		}
		public void testReturnValueForTableRemove() {
			runFragment(LuaValue.NONE, "return table.remove({ })");
		}
		public void testTypeOfTableRemoveReturnValue() {
			runFragment(LuaValue.valueOf("nil"), "local k = table.remove({ }) return type(k)");
		}
		public void testVarargBugReport() {
			runFragment(LuaValue.varargsOf(new LuaValue[] { 
					LuaValue.valueOf(1), LuaValue.valueOf(2), LuaValue.valueOf(3) }),
					"local i = function(...) return ... end\n"
					+ "local v1, v2, v3 = i(1, 2, 3)\n"
					+ "return v1, v2, v3");
			
		}

		private static File writeTempFile(String prefix, String contents) throws IOException {
			File file = File.createTempFile(prefix, ".txt");
			FileOutputStream out = new FileOutputStream(file);
			try {
				out.write(contents.getBytes("UTF-8"));
			} finally {
				out.close();
			}
			return file;
		}

		private static String quote(String value) {
			return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
		}
	}
}
