<commit_before># -*- coding: utf-8 -*-
from __future__ import absolute_import
from base64 import standard_b64decode
import json
import unittest
from cStringIO import StringIO
import numbers
import time

from PIL import Image
import requests
import pytest
lupa = pytest.importorskip("lupa")

from . import test_render
from .test_jsonpost import JsonPostRequestHandler
from .utils import NON_EXISTING_RESOLVABLE, SplashServer
from .mockserver import JsRender
from .. import defaults


class BaseLuaRenderTest(test_render.BaseRenderTest):
    endpoint = 'execute'

    def request_lua(self, code, query=None):
        q = {"lua_source": code}
        q.update(query or {})
        return self.request(q)

    def assertErrorLineNumber(self, resp, linenum):
        self.assertStatusCode(resp, 400)
        self.assertIn(":%d:" % linenum, resp.text)


class MainFunctionTest(BaseLuaRenderTest):

    def test_return_json(self):
        resp = self.request_lua("""
        function main(splash)
          local obj = {key="value"}
          return {
            mystatus="ok",
            number=5,
            float=-0.5,
            obj=obj,
            bool=true,
            bool2=false,
            missing=nil
          }
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'application/json')
        self.assertEqual(resp.json(), {
            "mystatus": "ok",
            "number": 5,
            "float": -0.5,
            "obj": {"key": "value"},
            "bool": True,
            "bool2": False,
        })

    def test_unicode(self):
        resp = self.request_lua(u"""
        function main(splash) return {key="значение"} end
        """.encode('utf8'))

        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'application/json')
        self.assertEqual(resp.json(), {"key": u"значение"})

    def test_unicode_direct(self):
        resp = self.request_lua(u"""
        function main(splash)
          return 'привет'
        end
        """.encode('utf8'))
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, u"привет")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_number(self):
        resp = self.request_lua("function main(splash) return 1 end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "1")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_number_float(self):
        resp = self.request_lua("function main(splash) return 1.5 end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "1.5")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_bool(self):
        resp = self.request_lua("function main(splash) return true end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "True")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_empty(self):
        resp = self.request_lua("function main(splash) end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "")

        resp = self.request_lua("function main() end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "")

    def test_no_main(self):
        resp = self.request_lua("x=1")
        self.assertStatusCode(resp, 400)
        self.assertIn("function is not found", resp.text)

    def test_bad_main(self):
        resp = self.request_lua("main=1")
        self.assertStatusCode(resp, 400)
        self.assertIn("is not a function", resp.text)

    def test_ugly_main(self):
        resp = self.request_lua("main={coroutine=123}")
        self.assertStatusCode(resp, 400)
        self.assertIn("is not a function", resp.text)

    def test_nasty_main(self):
        resp = self.request_lua("""
        main = {coroutine=function()
          return {
            send=function() end,
            next=function() end
          }
        end}
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("is not a function", resp.text)


class ResultContentTypeTest(BaseLuaRenderTest):
    def test_content_type(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type('text/plain')
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'text/plain')
        self.assertEqual(resp.text, 'hi!')

    def test_content_type_ignored_for_tables(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type('text/plain')
          return {hi="hi!"}
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'application/json')
        self.assertEqual(resp.text, '{"hi": "hi!"}')

    def test_bad_content_type(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type(55)
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("argument must be a string", resp.text)

        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type()
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("set_result_content_type", resp.text)

    def test_bad_content_type_func(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type(function () end)
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("argument must be a string", resp.text)


class ErrorsTest(BaseLuaRenderTest):

    def test_syntax_error(self):
        resp = self.request_lua("function main(splash) sdhgfsajhdgfjsahgd end")
        self.assertStatusCode(resp, 400)

    def test_syntax_error_toplevel(self):
        resp = self.request_lua("sdg; function main(splash) sdhgfsajhdgfjsahgd end")
        self.assertStatusCode(resp, 400)

    def test_unicode_error(self):
        resp = self.request_lua(u"function main(splash) 'привет' end".encode('utf8'))
        self.assertStatusCode(resp, 400)
        self.assertIn("unexpected symbol", resp.text)

    def test_user_error(self):
        resp = self.request_lua("""
        function main(splash)
          error("User Error Happened")
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("User Error Happened", resp.text)

    def test_bad_splash_attribute(self):
        resp = self.request_lua("""
        function main(splash)
          local x = splash.foo
          return x == nil
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "True")

    def test_return_multiple(self):
        resp = self.request_lua("function main(splash) return 'foo', 'bar' end")
        self.assertStatusCode(resp, 400)
        self.assertIn("must return a single result", resp.text)

    def test_return_splash(self):
        resp = self.request_lua("function main(splash) return splash end")
        self.assertStatusCode(resp, 400)

    def test_return_function(self):
        resp = self.request_lua("function main(splash) return function() end end")
        self.assertStatusCode(resp, 400)
        self.assertIn("function objects are not allowed", resp.text)

    def test_return_coroutine(self):
        resp = self.request_lua("""
        function main(splash)
          return coroutine.create(function() end)
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("(a nil value)", resp.text)

    def test_return_coroutine_nosandbox(self):
        with SplashServer(extra_args=['--disable-lua-sandbox']) as splash:
            resp = requests.get(
                url=splash.url("execute"),
                params={
                    'lua_source': """
                        function main(splash)
                            return coroutine.create(function() end)
                        end
                    """
                },
            )
            self.assertStatusCode(resp, 400)
            self.assertIn("function objects are not allowed", resp.text)

    def test_return_started_coroutine(self):
        resp = self.request_lua("""
        function main(splash)
          local co = coroutine.create(function()
            coroutine.yield()
          end)
          coroutine.resume(co)
          return co
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("(a nil value)", resp.text)

    def test_return_started_coroutine_nosandbox(self):
        with SplashServer(extra_args=['--disable-lua-sandbox']) as splash:
            resp = requests.get(
                url=splash.url("execute"),
                params={
                    'lua_source': """
                        function main(splash)
                          local co = coroutine.create(function()
                            coroutine.yield()
                          end)
                          coroutine.resume(co)
                          return co
                        end
                    """
                },
            )
            self.assertStatusCode(resp, 400)
            self.assertIn("thread objects are not allowed", resp.text)

    def test_error_line_number_attribute_access(self):
        resp = self.request_lua("""
        function main(splash)
           local x = 5
           splash.set_result_content_type("hello")
        end
        """)
        self.assertErrorLineNumber(resp, 4)

    def test_error_line_number_bad_argument(self):
        resp = self.request_lua("""
        function main(splash)
           local x = 5
           splash:set_result_content_type(48)
        end
        """)
        self.assertErrorLineNumber(resp, 4)

    def test_error_line_number_wrong_keyword_argument(self):
        resp = self.request_lua("""  -- 1
        function main(splash)        -- 2
           splash:wait{timeout=0.7}  -- 3 <--
        end                          -- 4
        """)                       # -- 5
        self.assertErrorLineNumber(resp, 3)

    def test_pcall_wrong_keyword_arguments(self):
        resp = self.request_lua("""
        function main(splash)
           local x = function()
               return splash:wait{timeout=0.7}
           end
           local ok, res = pcall(x)
           return {ok=ok, res=res}
        end
        """)
        self.assertStatusCode(resp, 200)
        data = resp.json()
        self.assertEqual(data["ok"], False)



class EvaljsTest(BaseLuaRenderTest):

    def _evaljs_request(self, js):
        return self.request_lua("""
        function main(splash)
            local res = splash:evaljs([[%s]])
            return {res=res, tp=type(res)}
        end
        """ % js)

    def assertEvaljsResult(self, js, result, type):
        resp = self._evaljs_request(js)
        self.assertStatusCode(resp, 200)
        expected = {'tp': type}
        if result is not None:
            expected['res'] = result
        self.assertEqual(resp.json(), expected)

    def assertEvaljsError(self, js, error_parts="JsError"):
        resp = self._evaljs_request(js)
        self.assertStatusCode(resp, 400)
        if isinstance(error_parts, (bytes, unicode)):
            error_parts = [error_parts]
        for part in error_parts:
            self.assertIn(part, resp.text)

    def test_numbers(self):
        self.assertEvaljsResult("1.0", 1.0, "number")
        self.assertEvaljsResult("1", 1, "number")
        self.assertEvaljsResult("1+2", 3, "number")

    def test_inf(self):
        self.assertEvaljsResult("1/0", float('inf'), "number")
        self.assertEvaljsResult("-1/0", float('-inf'), "number")

    def test_string(self):
        self.assertEvaljsResult("'foo'", u'foo', 'string')

    def test_bool(self):
        self.assertEvaljsResult("true", True, 'boolean')

    def test_undefined(self):
        self.assertEvaljsResult("undefined", None, 'nil')

    def test_null(self):
        # XXX: null is converted to an empty string by QT,
        # we can't distinguish it from a "real" empty string.
        self.assertEvaljsResult("null", "", 'string')

    def test_unicode_string(self):
        self.assertEvaljsResult("'привет'", u'привет', 'string')

    def test_unicode_string_in_object(self):
        self.assertEvaljsResult(
            'var o={}; o["ключ"] = "значение"; o',
            {u'ключ': u'значение'},
            'table'
        )

    def test_nested_object(self):
        self.assertEvaljsResult(
            'var o={}; o["x"] = {}; o["x"]["y"] = 5; o["z"] = "foo"; o',
            {"x": {"y": 5}, "z": "foo"},
            'table'
        )

    def test_array(self):
        self.assertEvaljsResult(
            'x = [3, 2, 1, "foo", ["foo", [], "bar"], {}]; x',
            [3, 2, 1, "foo", ["foo", [], "bar"], {}],
            'table',
        )

    def test_self_referencing(self):
        self.assertEvaljsResult(
            'var o={}; o["x"] = "5"; o["y"] = o; o',
            {"x": "5"},  # self reference is discarded
            'table'
        )

    def test_function(self):
        # XXX: functions are not returned by QT
        self.assertEvaljsResult(
            "x = function(){return 5}; x",
            {},
            "table"
        )

    def test_function_direct(self):
        # XXX: functions are not returned by QT
        self.assertEvaljsError("function(){return 5}")

    def test_object_with_function(self):
        # XXX: complex objects are unsupported
        self.assertEvaljsError('{"x":2, "y": function(){}}')

    def test_function_call(self):
        self.assertEvaljsResult(
            "function x(){return 5}; x();",
            5,
            "number"
        )

    def test_dateobj(self):
        # XXX: Date objects are converted to ISO8061 strings.
        # Does it make sense to do anything else with them?
        # E.g. make them available to Lua as tables?
        self.assertEvaljsResult(
            'x = new Date("21 May 1958 10:12 UTC"); x',
            "1958-05-21T10:12:00Z",
            "string"
        )

    def test_regexp(self):
        self.assertEvaljsResult(
            '/my-regexp/i',
            {
                u'_jstype': u'RegExp',
                'caseSensitive': False,
                'pattern': u'my-regexp'
            },
            'table'
        )

        self.assertEvaljsResult(
            '/my-regexp/',
            {
                u'_jstype': u'RegExp',
                'caseSensitive': True,
                'pattern': u'my-regexp'
            },
            'table'
        )

    def test_syntax_error(self):
        self.assertEvaljsError("x--4", ["JsError", "SyntaxError"])

    def test_throw_string(self):
        self.assertEvaljsError(
            "(function(){throw 'ABC'})();",
            ["JsError", "ABC"],
        )
        self.assertEvaljsError("throw 'ABC'", ["JsError", "ABC"])

    def test_throw_error(self):
        self.assertEvaljsError(
            "(function(){throw new Error('ABC')})();",
            ["JsError", "ABC"],
        )
        self.assertEvaljsError("throw new Error('ABC')", ["JsError", "Error: ABC"])


class WaitForResumeTest(BaseLuaRenderTest):

    def _wait_for_resume_request(self, js, timeout=1):
        return self.request_lua("""
        function main(splash)
            local result, error = splash:wait_for_resume([[%s]], %.1f)
            local response = {}

            if result ~= nil then
                response["value"] = result["value"]
                response["value_type"] = type(result["value"])
            else
                response["error"] = error
            end

            return response
        end
        """ % (js, timeout))

    def test_return_undefined(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume();
            }
        """)
        self.assertStatusCode(resp, 200)
        # A Lua table with a nil value is equivalent to not setting that
        # key/value pair at all, so there is no "result" key in the response.
        self.assertEqual(resp.json(), {"value_type": "nil"})

    def test_return_null(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(null);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": "", "value_type": "string"})

    def test_return_string(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume("ok");
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": "ok", "value_type": "string"})

    def test_return_non_ascii_string(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume("你好");
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": u"你好", "value_type": "string"})

    def test_return_int(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(42);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": 42, "value_type": "number"})

    def test_return_float(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(1234.5);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": 1234.5, "value_type": "number"})

    def test_return_boolean(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(true);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": True, "value_type": "boolean"})

    def test_return_list(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume([1,2,'red','blue']);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {
            "value": [1, 2, 'red', 'blue'],
            "value_type": "table"}
        )

    def test_return_dict(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume({'stomach':'empty','brain':'crazy'});
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {
            "value": {'stomach':'empty', 'brain':'crazy'},
            "value_type": "table"}
        )

    def test_return_additional_keys(self):
        resp = self.request_lua("""
        function main(splash)
            local result, error = splash:wait_for_resume([[
                function main(splash) {
                    splash.set("foo", "bar");
                    splash.resume("ok");
                }
            ]])

            return result
        end""")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {'foo': 'bar', 'value': 'ok'})

    def test_delayed_return(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                setTimeout(function () {
                    splash.resume("ok");
                }, 500);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": "ok", "value_type": "string"})

    def test_error_string(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.error("not ok");
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"error": "JavaScript error: not ok"})

    def test_timed_out(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                setTimeout(function () {
                    splash.resume("ok");
                }, 2500);
            }
        """, timeout=0.5)
        expected_error = 'JavaScript error: One shot callback timed out' \
                         ' while waiting for resume() or error().'
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"error": expected_error})

    def test_missing_main_function(self):
        resp = self._wait_for_resume_request("""
            function foo(splash) {
                setTimeout(function () {<commit_msg>TST speedup tests a bit<commit_after># -*- coding: utf-8 -*-
from __future__ import absolute_import
from base64 import standard_b64decode
import json
import unittest
from cStringIO import StringIO
import numbers
import time

from PIL import Image
import requests
import pytest
lupa = pytest.importorskip("lupa")

from . import test_render
from .test_jsonpost import JsonPostRequestHandler
from .utils import NON_EXISTING_RESOLVABLE, SplashServer
from .mockserver import JsRender
from .. import defaults


class BaseLuaRenderTest(test_render.BaseRenderTest):
    endpoint = 'execute'

    def request_lua(self, code, query=None):
        q = {"lua_source": code}
        q.update(query or {})
        return self.request(q)

    def assertErrorLineNumber(self, resp, linenum):
        self.assertStatusCode(resp, 400)
        self.assertIn(":%d:" % linenum, resp.text)


class MainFunctionTest(BaseLuaRenderTest):

    def test_return_json(self):
        resp = self.request_lua("""
        function main(splash)
          local obj = {key="value"}
          return {
            mystatus="ok",
            number=5,
            float=-0.5,
            obj=obj,
            bool=true,
            bool2=false,
            missing=nil
          }
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'application/json')
        self.assertEqual(resp.json(), {
            "mystatus": "ok",
            "number": 5,
            "float": -0.5,
            "obj": {"key": "value"},
            "bool": True,
            "bool2": False,
        })

    def test_unicode(self):
        resp = self.request_lua(u"""
        function main(splash) return {key="значение"} end
        """.encode('utf8'))

        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'application/json')
        self.assertEqual(resp.json(), {"key": u"значение"})

    def test_unicode_direct(self):
        resp = self.request_lua(u"""
        function main(splash)
          return 'привет'
        end
        """.encode('utf8'))
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, u"привет")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_number(self):
        resp = self.request_lua("function main(splash) return 1 end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "1")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_number_float(self):
        resp = self.request_lua("function main(splash) return 1.5 end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "1.5")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_bool(self):
        resp = self.request_lua("function main(splash) return true end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "True")
        self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8')

    def test_empty(self):
        resp = self.request_lua("function main(splash) end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "")

        resp = self.request_lua("function main() end")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "")

    def test_no_main(self):
        resp = self.request_lua("x=1")
        self.assertStatusCode(resp, 400)
        self.assertIn("function is not found", resp.text)

    def test_bad_main(self):
        resp = self.request_lua("main=1")
        self.assertStatusCode(resp, 400)
        self.assertIn("is not a function", resp.text)

    def test_ugly_main(self):
        resp = self.request_lua("main={coroutine=123}")
        self.assertStatusCode(resp, 400)
        self.assertIn("is not a function", resp.text)

    def test_nasty_main(self):
        resp = self.request_lua("""
        main = {coroutine=function()
          return {
            send=function() end,
            next=function() end
          }
        end}
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("is not a function", resp.text)


class ResultContentTypeTest(BaseLuaRenderTest):
    def test_content_type(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type('text/plain')
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'text/plain')
        self.assertEqual(resp.text, 'hi!')

    def test_content_type_ignored_for_tables(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type('text/plain')
          return {hi="hi!"}
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.headers['content-type'], 'application/json')
        self.assertEqual(resp.text, '{"hi": "hi!"}')

    def test_bad_content_type(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type(55)
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("argument must be a string", resp.text)

        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type()
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("set_result_content_type", resp.text)

    def test_bad_content_type_func(self):
        resp = self.request_lua("""
        function main(splash)
          splash:set_result_content_type(function () end)
          return "hi!"
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("argument must be a string", resp.text)


class ErrorsTest(BaseLuaRenderTest):

    def test_syntax_error(self):
        resp = self.request_lua("function main(splash) sdhgfsajhdgfjsahgd end")
        self.assertStatusCode(resp, 400)

    def test_syntax_error_toplevel(self):
        resp = self.request_lua("sdg; function main(splash) sdhgfsajhdgfjsahgd end")
        self.assertStatusCode(resp, 400)

    def test_unicode_error(self):
        resp = self.request_lua(u"function main(splash) 'привет' end".encode('utf8'))
        self.assertStatusCode(resp, 400)
        self.assertIn("unexpected symbol", resp.text)

    def test_user_error(self):
        resp = self.request_lua("""
        function main(splash)
          error("User Error Happened")
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("User Error Happened", resp.text)

    def test_bad_splash_attribute(self):
        resp = self.request_lua("""
        function main(splash)
          local x = splash.foo
          return x == nil
        end
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.text, "True")

    def test_return_multiple(self):
        resp = self.request_lua("function main(splash) return 'foo', 'bar' end")
        self.assertStatusCode(resp, 400)
        self.assertIn("must return a single result", resp.text)

    def test_return_splash(self):
        resp = self.request_lua("function main(splash) return splash end")
        self.assertStatusCode(resp, 400)

    def test_return_function(self):
        resp = self.request_lua("function main(splash) return function() end end")
        self.assertStatusCode(resp, 400)
        self.assertIn("function objects are not allowed", resp.text)

    def test_return_coroutine(self):
        resp = self.request_lua("""
        function main(splash)
          return coroutine.create(function() end)
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("(a nil value)", resp.text)

    def test_return_coroutine_nosandbox(self):
        with SplashServer(extra_args=['--disable-lua-sandbox']) as splash:
            resp = requests.get(
                url=splash.url("execute"),
                params={
                    'lua_source': """
                        function main(splash)
                            return coroutine.create(function() end)
                        end
                    """
                },
            )
            self.assertStatusCode(resp, 400)
            self.assertIn("function objects are not allowed", resp.text)

    def test_return_started_coroutine(self):
        resp = self.request_lua("""
        function main(splash)
          local co = coroutine.create(function()
            coroutine.yield()
          end)
          coroutine.resume(co)
          return co
        end
        """)
        self.assertStatusCode(resp, 400)
        self.assertIn("(a nil value)", resp.text)

    def test_return_started_coroutine_nosandbox(self):
        with SplashServer(extra_args=['--disable-lua-sandbox']) as splash:
            resp = requests.get(
                url=splash.url("execute"),
                params={
                    'lua_source': """
                        function main(splash)
                          local co = coroutine.create(function()
                            coroutine.yield()
                          end)
                          coroutine.resume(co)
                          return co
                        end
                    """
                },
            )
            self.assertStatusCode(resp, 400)
            self.assertIn("thread objects are not allowed", resp.text)

    def test_error_line_number_attribute_access(self):
        resp = self.request_lua("""
        function main(splash)
           local x = 5
           splash.set_result_content_type("hello")
        end
        """)
        self.assertErrorLineNumber(resp, 4)

    def test_error_line_number_bad_argument(self):
        resp = self.request_lua("""
        function main(splash)
           local x = 5
           splash:set_result_content_type(48)
        end
        """)
        self.assertErrorLineNumber(resp, 4)

    def test_error_line_number_wrong_keyword_argument(self):
        resp = self.request_lua("""  -- 1
        function main(splash)        -- 2
           splash:wait{timeout=0.7}  -- 3 <--
        end                          -- 4
        """)                       # -- 5
        self.assertErrorLineNumber(resp, 3)

    def test_pcall_wrong_keyword_arguments(self):
        resp = self.request_lua("""
        function main(splash)
           local x = function()
               return splash:wait{timeout=0.7}
           end
           local ok, res = pcall(x)
           return {ok=ok, res=res}
        end
        """)
        self.assertStatusCode(resp, 200)
        data = resp.json()
        self.assertEqual(data["ok"], False)



class EvaljsTest(BaseLuaRenderTest):

    def _evaljs_request(self, js):
        return self.request_lua("""
        function main(splash)
            local res = splash:evaljs([[%s]])
            return {res=res, tp=type(res)}
        end
        """ % js)

    def assertEvaljsResult(self, js, result, type):
        resp = self._evaljs_request(js)
        self.assertStatusCode(resp, 200)
        expected = {'tp': type}
        if result is not None:
            expected['res'] = result
        self.assertEqual(resp.json(), expected)

    def assertEvaljsError(self, js, error_parts="JsError"):
        resp = self._evaljs_request(js)
        self.assertStatusCode(resp, 400)
        if isinstance(error_parts, (bytes, unicode)):
            error_parts = [error_parts]
        for part in error_parts:
            self.assertIn(part, resp.text)

    def test_numbers(self):
        self.assertEvaljsResult("1.0", 1.0, "number")
        self.assertEvaljsResult("1", 1, "number")
        self.assertEvaljsResult("1+2", 3, "number")

    def test_inf(self):
        self.assertEvaljsResult("1/0", float('inf'), "number")
        self.assertEvaljsResult("-1/0", float('-inf'), "number")

    def test_string(self):
        self.assertEvaljsResult("'foo'", u'foo', 'string')

    def test_bool(self):
        self.assertEvaljsResult("true", True, 'boolean')

    def test_undefined(self):
        self.assertEvaljsResult("undefined", None, 'nil')

    def test_null(self):
        # XXX: null is converted to an empty string by QT,
        # we can't distinguish it from a "real" empty string.
        self.assertEvaljsResult("null", "", 'string')

    def test_unicode_string(self):
        self.assertEvaljsResult("'привет'", u'привет', 'string')

    def test_unicode_string_in_object(self):
        self.assertEvaljsResult(
            'var o={}; o["ключ"] = "значение"; o',
            {u'ключ': u'значение'},
            'table'
        )

    def test_nested_object(self):
        self.assertEvaljsResult(
            'var o={}; o["x"] = {}; o["x"]["y"] = 5; o["z"] = "foo"; o',
            {"x": {"y": 5}, "z": "foo"},
            'table'
        )

    def test_array(self):
        self.assertEvaljsResult(
            'x = [3, 2, 1, "foo", ["foo", [], "bar"], {}]; x',
            [3, 2, 1, "foo", ["foo", [], "bar"], {}],
            'table',
        )

    def test_self_referencing(self):
        self.assertEvaljsResult(
            'var o={}; o["x"] = "5"; o["y"] = o; o',
            {"x": "5"},  # self reference is discarded
            'table'
        )

    def test_function(self):
        # XXX: functions are not returned by QT
        self.assertEvaljsResult(
            "x = function(){return 5}; x",
            {},
            "table"
        )

    def test_function_direct(self):
        # XXX: functions are not returned by QT
        self.assertEvaljsError("function(){return 5}")

    def test_object_with_function(self):
        # XXX: complex objects are unsupported
        self.assertEvaljsError('{"x":2, "y": function(){}}')

    def test_function_call(self):
        self.assertEvaljsResult(
            "function x(){return 5}; x();",
            5,
            "number"
        )

    def test_dateobj(self):
        # XXX: Date objects are converted to ISO8061 strings.
        # Does it make sense to do anything else with them?
        # E.g. make them available to Lua as tables?
        self.assertEvaljsResult(
            'x = new Date("21 May 1958 10:12 UTC"); x',
            "1958-05-21T10:12:00Z",
            "string"
        )

    def test_regexp(self):
        self.assertEvaljsResult(
            '/my-regexp/i',
            {
                u'_jstype': u'RegExp',
                'caseSensitive': False,
                'pattern': u'my-regexp'
            },
            'table'
        )

        self.assertEvaljsResult(
            '/my-regexp/',
            {
                u'_jstype': u'RegExp',
                'caseSensitive': True,
                'pattern': u'my-regexp'
            },
            'table'
        )

    def test_syntax_error(self):
        self.assertEvaljsError("x--4", ["JsError", "SyntaxError"])

    def test_throw_string(self):
        self.assertEvaljsError(
            "(function(){throw 'ABC'})();",
            ["JsError", "ABC"],
        )
        self.assertEvaljsError("throw 'ABC'", ["JsError", "ABC"])

    def test_throw_error(self):
        self.assertEvaljsError(
            "(function(){throw new Error('ABC')})();",
            ["JsError", "ABC"],
        )
        self.assertEvaljsError("throw new Error('ABC')", ["JsError", "Error: ABC"])


class WaitForResumeTest(BaseLuaRenderTest):

    def _wait_for_resume_request(self, js, timeout=1):
        return self.request_lua("""
        function main(splash)
            local result, error = splash:wait_for_resume([[%s]], %.1f)
            local response = {}

            if result ~= nil then
                response["value"] = result["value"]
                response["value_type"] = type(result["value"])
            else
                response["error"] = error
            end

            return response
        end
        """ % (js, timeout))

    def test_return_undefined(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume();
            }
        """)
        self.assertStatusCode(resp, 200)
        # A Lua table with a nil value is equivalent to not setting that
        # key/value pair at all, so there is no "result" key in the response.
        self.assertEqual(resp.json(), {"value_type": "nil"})

    def test_return_null(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(null);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": "", "value_type": "string"})

    def test_return_string(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume("ok");
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": "ok", "value_type": "string"})

    def test_return_non_ascii_string(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume("你好");
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": u"你好", "value_type": "string"})

    def test_return_int(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(42);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": 42, "value_type": "number"})

    def test_return_float(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(1234.5);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": 1234.5, "value_type": "number"})

    def test_return_boolean(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume(true);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": True, "value_type": "boolean"})

    def test_return_list(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume([1,2,'red','blue']);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {
            "value": [1, 2, 'red', 'blue'],
            "value_type": "table"}
        )

    def test_return_dict(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.resume({'stomach':'empty','brain':'crazy'});
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {
            "value": {'stomach':'empty', 'brain':'crazy'},
            "value_type": "table"}
        )

    def test_return_additional_keys(self):
        resp = self.request_lua("""
        function main(splash)
            local result, error = splash:wait_for_resume([[
                function main(splash) {
                    splash.set("foo", "bar");
                    splash.resume("ok");
                }
            ]])

            return result
        end""")
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {'foo': 'bar', 'value': 'ok'})

    def test_delayed_return(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                setTimeout(function () {
                    splash.resume("ok");
                }, 100);
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"value": "ok", "value_type": "string"})

    def test_error_string(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                splash.error("not ok");
            }
        """)
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"error": "JavaScript error: not ok"})

    def test_timed_out(self):
        resp = self._wait_for_resume_request("""
            function main(splash) {
                setTimeout(function () {
                    splash.resume("ok");
                }, 2500);
            }
        """, timeout=0.1)
        expected_error = 'JavaScript error: One shot callback timed out' \
                         ' while waiting for resume() or error().'
        self.assertStatusCode(resp, 200)
        self.assertEqual(resp.json(), {"error": expected_error})

    def test_missing_main_function(self):
        resp = self._wait_for_resume_request("""
            function foo(splash) {
                setTimeout(function () {
{'fraction_non_alphanumeric': 0.1305759862451046, 'fraction_numerical': 0.013038494603113956, 'mean_word_length': 2.0212105908664597, 'pattern_counts': {'":': 76, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3489471', 'n_tokens_mistral': 12725, 'n_tokens_neox': 12142, 'n_words': 2426}