diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..a1715720 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,8 @@ +# chore(linting): lint all python files +aae30e864449442cf0b04e94f8a242b1b667de9a + +# chore(linting): lint all JavaScript files +16dc3153b3cb684ca72445ed058babc8f5d97f42 + +# chore(linting): lint all C++ files +58cd4b45777b046f03a63255c1d93e289e1cab5e \ No newline at end of file diff --git a/.github/workflows/test-and-publish.yaml b/.github/workflows/test-and-publish.yaml index 791d317e..d56d6980 100644 --- a/.github/workflows/test-and-publish.yaml +++ b/.github/workflows/test-and-publish.yaml @@ -9,16 +9,38 @@ on: workflow_call: workflow_dispatch: inputs: - debug_enabled: - type: boolean - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + debug_enabled_os: + type: choice + description: Optionally, choose an OS to run the build with SSH debugging on (https://github.com/fawazahmed0/action-debug) required: false - default: false - dump_cores: - type: boolean - description: 'Include core dumps in CI artifacts' + options: + - '' + - 'ubuntu-20.04' + - 'macos-12' + - 'macos-13' + - 'macos-14' + - 'windows-2019' + debug_enabled_python: + type: choice + description: Choose a Python version to run the build with SSH debugging on required: false - default: false + options: + - '' + - '3.8' + - '3.9' + - '3.10' + - '3.11' + - '3.12' + build_type: + type: choice + description: 'Choose the build type to use' + required: false + default: 'Debug' + options: + - 'Debug' + - 'Profile' + - 'DRelease' + - 'Release' pull_request: env: @@ -100,11 +122,10 @@ jobs: fail-fast: false matrix: # The lowest supported version is Ubuntu 20.04 + Python 3.8 or macOS 12 + Python 3.9 - os: [ 'ubuntu-20.04', 'macos-12', 'windows-2019', 'macos-14' ] + os: [ 'ubuntu-20.04', 'macos-12', 'macos-13', 'macos-14', 'windows-2019' ] python_version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] exclude: # macOS 12 comes with Python 3.9 by default, so we drop ci support for Python 3.8 on macOS - # FIXME: We can't build on macOS 11 for now because our prebuilt `uncrustify` binary requires macOS 12 - os: 'macos-12' python_version: '3.8' # actions/setup-python: The version '3.8'/'3.9' with architecture 'arm64' was not found for macOS. @@ -165,7 +186,8 @@ jobs: - name: Build wheel run: | echo $(poetry run python --version) - poetry build --format=wheel + WORKFLOW_BUILD_TYPE=${{ inputs.build_type }} + BUILD_TYPE=${WORKFLOW_BUILD_TYPE:-"Debug"} poetry build --format=wheel ls -lah ./dist/ - name: Upload wheel as CI artifacts uses: actions/upload-artifact@v3 @@ -179,7 +201,7 @@ jobs: name: docs-${{ github.run_id }}-${{ github.sha }} path: ./build/docs/html/ - name: Set cores to get stored in /cores - if: ${{ matrix.os != 'windows-2019' && github.event_name == 'workflow_dispatch' && inputs.dump_cores }} + if: ${{ matrix.os != 'windows-2019' }} # TODO (Caleb Aikens) figure out how to get Windows core dumps run: | sudo mkdir -p /cores @@ -196,7 +218,8 @@ jobs: # TODO (Caleb Aikens) figure out how to get Windows core dumps ulimit -c unlimited fi - poetry run python -m pip install --force-reinstall --verbose ./dist/* + WORKFLOW_BUILD_TYPE=${{ inputs.build_type }} + BUILD_TYPE=${WORKFLOW_BUILD_TYPE:-"Debug"} poetry run python -m pip install --force-reinstall --verbose ./dist/* poetry run python -m pytest tests/python - name: Run JS tests (peter-jr) if: ${{ (success() || failure()) }} @@ -206,20 +229,18 @@ jobs: ulimit -c unlimited fi poetry run bash ./peter-jr ./tests/js/ + - name: SSH debug session + if: ${{ (success() || failure()) && github.event_name == 'workflow_dispatch' && inputs.debug_enabled_os == matrix.os && inputs.debug_enabled_python == matrix.python_version}} + uses: fawazahmed0/action-debug@main + with: + credentials: "admin:admin" - name: Upload core dumps as CI artifacts uses: actions/upload-artifact@v3 - if: ${{ matrix.os != 'windows-2019' && github.event_name == 'workflow_dispatch' && inputs.dump_cores }} + if: ${{ matrix.os != 'windows-2019' && failure() }} # TODO (Caleb Aikens) figure out how to get Windows core dumps with: name: cores-${{ matrix.os }}-${{ matrix.python_version }} path: /cores - # Enable tmate debugging of manually-triggered workflows if the input option was provided - - name: SSH debug session - if: ${{ (success() || failure()) && github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} - uses: mxschmitt/action-tmate@v3 - with: - detached: true - limit-access-to-actor: true sdist: runs-on: ubuntu-20.04 steps: @@ -236,7 +257,7 @@ jobs: - name: Build source distribution (sdist) file run: | poetry self add "poetry-dynamic-versioning[plugin]" - poetry build --format=sdist + BUILD_DOCS=1 BUILD_TYPE=Release poetry build --format=sdist ls -lah ./dist/ - name: Upload sdist as CI artifacts uses: actions/upload-artifact@v3 diff --git a/.gitignore b/.gitignore index 6cec5d71..00be16f6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ firefox-*/ __pycache__ Testing/Temporary _spidermonkey_install +uncrustify-*.tar.gz +uncrustify-*/ +uncrustify +*.uncrustify __pycache__/* dist *.so diff --git a/CMakeLists.txt b/CMakeLists.txt index 864c289a..85df24bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,23 +17,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# Add an external; appends to `PYTHONMONKEY_EXTERNAL_FILES` in the parent scope. -function(pythonmonkey_add_external PYTHONMONKEY_EXTERNAL) - add_subdirectory("cmake/externals/${PYTHONMONKEY_EXTERNAL}") - set(PYTHONMONKEY_EXTERNAL_FILE "cmake/externals/${PYTHONMONKEY_EXTERNAL}/CMakeLists.txt") - source_group( - TREE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/externals/${PYTHONMONKEY_EXTERNAL}" - PREFIX "Externals\\${PYTHONMONKEY_EXTERNAL}" - FILES "${PYTHONMONKEY_EXTERNAL_FILE}" - ) - list(APPEND PYTHONMONKEY_EXTERNAL_FILES "${PYTHONMONKEY_EXTERNAL_FILE}") - - set(PYTHONMONKEY_EXTERNAL_FILES ${PYTHONMONKEY_EXTERNAL_FILES} PARENT_SCOPE) -endfunction() - -file (GLOB SOURCE_FILES "src/*.cc" "src/internalBinding/*.cc") # Find all C++ files in the src directory -file (GLOB HEADER_FILES "include/*.hh") # Find all header files in the include directory -file (GLOB PYTHON_FILES "python/*.cc" "python/*.hh") # Find all the python bindings in the python directory +file (GLOB_RECURSE HEADER_FILES "include/*.hh") # Find all header files in the include directory and below +file (GLOB_RECURSE SOURCE_FILES "src/*.cc") # Find all C++ files in the src directory and below + include_directories(${CMAKE_CURRENT_LIST_DIR}) @@ -43,8 +29,50 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) ### Code block from: https://cliutils.gitlab.io/modern-cmake/chapters/projects/submodule.html include(FetchContent) - SET(COMPILE_FLAGS "-ggdb -Ofast -fno-rtti") # optimize but also emit debug symbols - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILE_FLAGS} $ENV{EXTRA_CMAKE_CXX_FLAGS}") + if (WIN32) + SET(COMPILE_FLAGS "/GR- /W0") + + SET(OPTIMIZED "/O2") + SET(UNOPTIMIZED "/Od") + SET(KEEP_SYMBOLS "/DEBUG:FULL") + SET(STRIP_SYMBOLS "/DEBUG:NONE") + SET(PROFILE "/PROFILE") + else() + SET(COMPILE_FLAGS "-fno-rtti -Wno-invalid-offsetof") + + SET(OPTIMIZED "-Ofast -DNDEBUG") + SET(UNOPTIMIZED "-O0") + SET(KEEP_SYMBOLS "-ggdb") + SET(STRIP_SYMBOLS "-s") + SET(PROFILE "-pg") + endif() + SET(PROFILE_FLAGS "${UNOPTIMIZED} ${KEEP_SYMBOLS} ${PROFILE}") + SET(DEBUG_FLAGS "${UNOPTIMIZED} ${KEEP_SYMBOLS}") + SET(DRELEASE_FLAGS "${OPTIMIZED} ${KEEP_SYMBOLS}") + SET(RELEASE_FLAGS "${OPTIMIZED} ${STRIP_SYMBOLS}") + + if(GENERATOR_IS_MULTI_CONFIG) + set(CMAKE_CONFIGURATION_TYPES "Profile;Debug;DRelease;Release" CACHE STRING "" FORCE) + string(APPEND COMPILE_FLAGS "$<$:${PROFILE_FLAGS}> $<$:${DEBUG_FLAGS}> $<$:${DRELEASE_FLAGS}> $<$:${RELEASE_FLAGS}>") + else() + set_property(CACHE PM_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build") + set_property(CACHE PM_BUILD_TYPE PROPERTY STRINGS "Profile;Debug;DRelease;Release") + if(PM_BUILD_TYPE STREQUAL "Profile") + list(APPEND COMPILE_FLAGS "${PROFILE_FLAGS}") + elseif(PM_BUILD_TYPE STREQUAL "Debug") + list(APPEND COMPILE_FLAGS "${DEBUG_FLAGS}") + elseif(PM_BUILD_TYPE STREQUAL "DRelease") + list(APPEND COMPILE_FLAGS "${DRELEASE_FLAGS}") + else() #Release build + message("PM_BUILD_TYPE not detected or invalid value, defaulting to Release build.") + set(PM_BUILD_TYPE Release CACHE STRING "" FORCE) + list(APPEND COMPILE_FLAGS "${RELEASE_FLAGS}") + endif() + message("PythonMonkey build type is: ${PM_BUILD_TYPE}") + list(JOIN COMPILE_FLAGS " " COMPILE_FLAGS) + endif() + + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILE_FLAGS}") set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) if(APPLE) @@ -55,36 +83,35 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) set(PYTHONLIBS_VERSION_STRING ${Python_VERSION}) set(PYTHON_INCLUDE_DIR ${Python_INCLUDE_DIRS}) set(PYTHON_LIBRARIES ${Python_LIBRARIES}) - message("Apple - Using Python:${Python_VERSION_MAJOR} - Libraries:${PYTHON_LIBRARIES} - IncludeDirs: ${PYTHON_INCLUDE_DIR}") elseif(UNIX) find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED) set(Python_FIND_VIRTUALENV FIRST) # (require cmake >= v3.15 and this is the default) use the Python version configured by pyenv if available set(PYTHON_LIBRARIES ${Python_LIBRARIES}) set(PYTHON_INCLUDE_DIR ${Python_INCLUDE_DIRS}) - message("Linux - Using Python:${Python_VERSION_MAJOR}.${Python_VERSION_MINOR} - Libraries:${PYTHON_LIBRARIES} - IncludeDirs: ${PYTHON_INCLUDE_DIR}") find_package(SpiderMonkey REQUIRED) elseif(WIN32) - find_package(PythonInterp 3.8 REQUIRED) - find_package(PythonLibs 3.8 REQUIRED) + find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED) + set(Python_FIND_VIRTUALENV FIRST) # (require cmake >= v3.15 and this is the default) use the Python version configured by pyenv if available + set(PYTHON_LIBRARIES ${Python_LIBRARIES}) + set(PYTHON_INCLUDE_DIR ${Python_INCLUDE_DIRS}) find_package(SpiderMonkey REQUIRED) - set(PYTHONLIBS_VERSION_STRING $ENV{PY_VERSION}) endif() - include_directories(${PYTHON_INCLUDE_DIRS}) + message("${CMAKE_SYSTEM_NAME} - Using Python:${Python_VERSION} - Libraries:${Python_LIBRARIES} - IncludeDirs: ${Python_INCLUDE_DIRS}") + include_directories(${Python_INCLUDE_DIRS}) include_directories(${SPIDERMONKEY_INCLUDE_DIR}) # Add doxygen if this is the main app - find_package(Doxygen) - if(Doxygen_FOUND) + option(BUILD_DOCS "Build documentation" OFF) + if(BUILD_DOCS) + find_package(Doxygen) + if(Doxygen_FOUND) add_subdirectory(cmake/docs) - else() + else() message(STATUS "Doxygen not found. Not building docs.") + endif() endif() endif() # Add compiled folder directories add_subdirectory(src) - -pythonmonkey_add_external("uncrustify") -pythonmonkey_add_external("autopep8") -add_subdirectory(cmake/format) diff --git a/Makefile b/Makefile index f54b6c0b..37e7ef09 100644 --- a/Makefile +++ b/Makefile @@ -4,24 +4,37 @@ # @date March 2024 # -BUILD = debug +BUILD = Debug # (case-insensitive) Release, DRelease, Debug, or Profile +DOCS = false +VERBOSE = true PYTHON = python3 -RUN = poetry run +RUN = poetry run -PYTHON_BUILD_ENV = VERBOSE=1 EXTRA_CMAKE_CXX_FLAGS="$(EXTRA_CMAKE_CXX_FLAGS)" OS_NAME := $(shell uname -s) ifeq ($(OS_NAME),Linux) -CPU_COUNT=$(shell cat /proc/cpuinfo | grep -c processor) -MAX_JOBS=10 +CPU_COUNT = $(shell cat /proc/cpuinfo | grep -c processor) +MAX_JOBS = 10 CPUS := $(shell test $(CPU_COUNT) -lt $(MAX_JOBS) && echo $(CPU_COUNT) || echo $(MAX_JOBS)) PYTHON_BUILD_ENV += CPUS=$(CPUS) endif -EXTRA_CMAKE_CXX_FLAGS = -Wno-invalid-offsetof $(JOBS) +ifeq ($(BUILD),Profile) +PYTHON_BUILD_ENV += BUILD_TYPE=Profile +else ifeq ($(BUILD),Debug) +PYTHON_BUILD_ENV += BUILD_TYPE=Debug +else ifeq ($(BUILD),DRelease) +PYTHON_BUILD_ENV += BUILD_TYPE=DRelease +else # Release build +PYTHON_BUILD_ENV += BUILD_TYPE=Release +endif + +ifeq ($(DOCS),true) +PYTHON_BUILD_ENV += BUILD_DOCS=1 +endif -ifeq ($(BUILD),debug) -EXTRA_CMAKE_CXX_FLAGS += -O0 +ifeq ($(VERBOSE),true) +PYTHON_BUILD_ENV += VERBOSE=1 endif .PHONY: build test all clean debug @@ -37,10 +50,9 @@ all: build test clean: rm -rf build/src/CMakeFiles/pythonmonkey.dir rm -f build/src/pythonmonkey.so - rm -f python/pythonmonkey.so + rm -f python/pythonmonkey/pythonmonkey.so debug: - @echo EXTRA_CMAKE_CXX_FLAGS=$(EXTRA_CMAKE_CXX_FLAGS) @echo JOBS=$(JOBS) @echo CPU_COUNT=$(CPU_COUNT) - @echo OS_NAME=$(OS_NAME) + @echo OS_NAME=$(OS_NAME) \ No newline at end of file diff --git a/README.md b/README.md index 61905055..2ca26003 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,12 @@ Read this if you want to build a local version. - [Poetry](https://python-poetry.org/docs/#installation) - [poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning) -2. Run `poetry install`. This command automatically compiles the project and installs the project as well as dependencies into the poetry virtualenv. +2. Run `poetry install`. This command automatically compiles the project and installs the project as well as dependencies into the poetry virtualenv. If you would like to build the docs, set the `BUILD_DOCS` environment variable, like so: `BUILD_DOCS=1 poetry install`. +PythonMonkey supports multiple build types, which you can build by setting the `BUILD_TYPE` environment variable, like so: `BUILD_TYPE=Debug poetry install`. The build types are (case-insensitive): +- `Release`: stripped symbols, maximum optimizations (default) +- `DRelease`: same as `Release`, except symbols are not stripped +- `Debug`: minimal optimizations +- `Profile`: same as `Debug`, except profiling is enabled If you are using VSCode, you can just press Ctrl + Shift + B to [run build task](https://code.visualstudio.com/docs/editor/tasks#_custom-tasks) - We have [the `tasks.json` file configured for you](.vscode/tasks.json). diff --git a/build.py b/build.py index 0bcb1ab0..bddd00f3 100644 --- a/build.py +++ b/build.py @@ -6,7 +6,8 @@ # import subprocess -import os, sys +import os +import sys import platform from typing import Optional @@ -16,47 +17,62 @@ # Get number of CPU cores CPUS = os.getenv('CPUS') or os.cpu_count() or 1 + def execute(cmd: str, cwd: Optional[str] = None): - popen = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, - shell = True, text = True, cwd = cwd ) - for stdout_line in iter(popen.stdout.readline, ""): - sys.stdout.write(stdout_line) - sys.stdout.flush() + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + shell=True, text=True, cwd=cwd) + for stdout_line in iter(popen.stdout.readline, ""): + sys.stdout.write(stdout_line) + sys.stdout.flush() + + popen.stdout.close() + return_code = popen.wait() + if return_code: + raise subprocess.CalledProcessError(return_code, cmd) - popen.stdout.close() - return_code = popen.wait() - if return_code: - raise subprocess.CalledProcessError(return_code, cmd) def ensure_spidermonkey(): - # Check if SpiderMonkey libs already exist - spidermonkey_lib_exist = os.path.exists(os.path.join( TOP_DIR, "_spidermonkey_install/lib" )) - if spidermonkey_lib_exist: - return + # Check if SpiderMonkey libs already exist + spidermonkey_lib_exist = os.path.exists(os.path.join(TOP_DIR, "_spidermonkey_install/lib")) + if spidermonkey_lib_exist: + return + + # Build SpiderMonkey + execute("bash ./setup.sh", cwd=TOP_DIR) + + +def ensure_githooks(): + execute("ln -s -f ../../githooks/pre-commit .git/hooks/pre-commit", cwd=TOP_DIR) - # Build SpiderMonkey - execute("bash ./setup.sh", cwd = TOP_DIR) def run_cmake_build(): - os.makedirs(BUILD_DIR, exist_ok=True) # mkdir -p - if platform.system() == "Windows": - execute("cmake .. -T ClangCL", cwd=BUILD_DIR) # use Clang/LLVM toolset for Visual Studio - else: - execute("cmake ..", cwd=BUILD_DIR) - execute(f"cmake --build . -j{CPUS} --config Release", cwd=BUILD_DIR) + os.makedirs(BUILD_DIR, exist_ok=True) # mkdir -p + build_type = os.environ["BUILD_TYPE"].title() if "BUILD_TYPE" in os.environ else "Release" + build_docs = "ON" if "BUILD_DOCS" in os.environ and os.environ["BUILD_DOCS"] in ("1", "ON", "on") else "OFF" + + if platform.system() == "Windows": + # use Clang/LLVM toolset for Visual Studio + execute(f"cmake -DBUILD_DOCS={build_docs} -DPM_BUILD_TYPE={build_type} .. -T ClangCL", cwd=BUILD_DIR) + else: + execute(f"cmake -DBUILD_DOCS={build_docs} -DPM_BUILD_TYPE={build_type} ..", cwd=BUILD_DIR) + execute(f"cmake --build . -j{CPUS} --config Release", cwd=BUILD_DIR) + def copy_artifacts(): - if platform.system() == "Windows": - execute("cp ./build/src/*/pythonmonkey.pyd ./python/pythonmonkey/", cwd=TOP_DIR) # Release or Debug build - execute("cp ./_spidermonkey_install/lib/mozjs-*.dll ./python/pythonmonkey/", cwd=TOP_DIR) - else: - execute("cp ./build/src/pythonmonkey.so ./python/pythonmonkey/", cwd=TOP_DIR) - execute("cp ./_spidermonkey_install/lib/libmozjs* ./python/pythonmonkey/", cwd=TOP_DIR) + if platform.system() == "Windows": + execute("cp ./build/src/*/pythonmonkey.pyd ./python/pythonmonkey/", cwd=TOP_DIR) # Release or Debug build + execute("cp ./_spidermonkey_install/lib/mozjs-*.dll ./python/pythonmonkey/", cwd=TOP_DIR) + else: + execute("cp ./build/src/pythonmonkey.so ./python/pythonmonkey/", cwd=TOP_DIR) + execute("cp ./_spidermonkey_install/lib/libmozjs* ./python/pythonmonkey/", cwd=TOP_DIR) + def build(): - ensure_spidermonkey() - run_cmake_build() - copy_artifacts() + ensure_spidermonkey() + ensure_githooks() + run_cmake_build() + copy_artifacts() + if __name__ == "__main__": - build() + build() diff --git a/cmake/docs/CMakeLists.txt b/cmake/docs/CMakeLists.txt index 4ada1336..4563c06d 100644 --- a/cmake/docs/CMakeLists.txt +++ b/cmake/docs/CMakeLists.txt @@ -1,23 +1,14 @@ -# first we can indicate the documentation build as an option and set it to ON by default -option(BUILD_DOC "Build documentation" ON) +# set input and output files +set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) +set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) -# check if Doxygen is installed -find_package(Doxygen) -if (DOXYGEN_FOUND) - # set input and output files - set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) - set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) +# request to configure the file +configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) +message("Building docs with Doxygen") - # request to configure the file - configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) - message("Doxygen build started") - - # note the option ALL which allows to build the docs together with the application - add_custom_target( doc_doxygen ALL - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" - VERBATIM ) -else (DOXYGEN_FOUND) - message("Doxygen need to be installed to generate the doxygen documentation") -endif (DOXYGEN_FOUND) \ No newline at end of file +# note the option ALL which allows to build the docs together with the application +add_custom_target( doc_doxygen ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM ) \ No newline at end of file diff --git a/cmake/docs/Doxyfile.in b/cmake/docs/Doxyfile.in index 094e53d1..d2ac1e7e 100644 --- a/cmake/docs/Doxyfile.in +++ b/cmake/docs/Doxyfile.in @@ -1,6 +1,5 @@ OUTPUT_DIRECTORY = "@CMAKE_BINARY_DIR@/docs/" INPUT = "@CMAKE_SOURCE_DIR@/README.md" \ - "@CMAKE_SOURCE_DIR@/docs/" \ "@CMAKE_SOURCE_DIR@/src/" \ "@CMAKE_SOURCE_DIR@/include/" \ "@CMAKE_SOURCE_DIR@/python/pythonmonkey/" @@ -20,8 +19,8 @@ GENERATE_XML = YES OPTIMIZE_OUTPUT_JAVA = YES STRIP_FROM_PATH = "@CMAKE_SOURCE_DIR@" -FILE_PATTERNS = *.cc *.hh *.py *.pyi *.js *.ts *.md -EXTENSION_MAPPING = pyi=python js=javascript ts=javascript +FILE_PATTERNS = *.cc *.hh *.py *.pyi *.md +EXTENSION_MAPPING = pyi=python # Doxygen Theme # https://jothepro.github.io/doxygen-awesome-css/ @@ -38,10 +37,9 @@ HTML_EXTRA_FILES = @CMAKE_CURRENT_SOURCE_DIR@/favicon.ico \ @CMAKE_CURRENT_SOURCE_DIR@/doxygen-awesome-css/doxygen-awesome-paragraph-link.js \ @CMAKE_CURRENT_SOURCE_DIR@/doxygen-awesome-css/doxygen-awesome-interactive-toc.js HTML_COLORSTYLE = LIGHT -HTML_TIMESTAMP = YES +TIMESTAMP = YES # Diagrams with Graphviz HAVE_DOT = YES DOT_IMAGE_FORMAT = svg -DOT_TRANSPARENT = YES INTERACTIVE_SVG = YES diff --git a/cmake/externals/autopep8/CMakeLists.txt b/cmake/externals/autopep8/CMakeLists.txt deleted file mode 100644 index ebc95787..00000000 --- a/cmake/externals/autopep8/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2022 Distributive Inc. All Rights Reserved. - -set(AUTOPEP8_ROOT "${CMAKE_CURRENT_BINARY_DIR}/install" CACHE PATH - "The autopep8 root directory." -) -if(NOT AUTOPEP8_ROOT STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/install") - return() -endif() - - set(AUTOPEP8_DOWNLOAD_PACKAGE_FILE_ID "5d/9b/1ed75f8c9086fafe0e9bbb379a70c43b1aa9dff6154ddcfb818f78cb0736/autopep8-1.7.0-py2.py3-none-any.whl") - set(AUTOPEP8_DOWNLOAD_SHA256 - "6f09e90a2be784317e84dc1add17ebfc7abe3924239957a37e5040e27d812087" - ) - -string(CONCAT AUTOPEP8_DOWNLOAD_URL - "https://files.pythonhosted.org/" - "packages/${AUTOPEP8_DOWNLOAD_PACKAGE_FILE_ID}" -) - -include("${CMAKE_ROOT}/Modules/FetchContent.cmake") - -FetchContent_Declare(autopep8 - DOWNLOAD_EXTRACT_TIMESTAMP FALSE - PREFIX "${CMAKE_CURRENT_BINARY_DIR}" - SOURCE_DIR "${AUTOPEP8_ROOT}" - URL "${AUTOPEP8_DOWNLOAD_URL}" - URL_HASH "SHA256=${AUTOPEP8_DOWNLOAD_SHA256}" - TLS_VERIFY TRUE -) -FetchContent_MakeAvailable(autopep8) diff --git a/cmake/externals/uncrustify/CMakeLists.txt b/cmake/externals/uncrustify/CMakeLists.txt deleted file mode 100644 index ba95422b..00000000 --- a/cmake/externals/uncrustify/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2022 Distributive Inc. All Rights Reserved. - -set(UNCRUSTIFY_ROOT "${CMAKE_CURRENT_BINARY_DIR}/install" CACHE PATH - "The Uncrustify root directory." -) -if(NOT UNCRUSTIFY_ROOT STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/install") - return() -endif() - -# 0.1.12 -if(CMAKE_SYSTEM_NAME MATCHES "^Darwin$") - set(UNCRUSTIFY_DOWNLOAD_PACKAGE_FILE_ID "47722390") - set(UNCRUSTIFY_DOWNLOAD_SHA256 - "f53c51c30f8482cf801bf4db11becc7ec62a7e86f0fc90878150e9a85bb7638b" - ) -elseif(CMAKE_SYSTEM_NAME MATCHES "^Linux$") - set(UNCRUSTIFY_DOWNLOAD_PACKAGE_FILE_ID "47722428") - set(UNCRUSTIFY_DOWNLOAD_SHA256 - "595a4634831777bf77612ca0223ef5cf8da9aac4aee885291a886bf9b358153d" - ) -elseif(CMAKE_SYSTEM_NAME MATCHES "^Windows$") - set(UNCRUSTIFY_DOWNLOAD_PACKAGE_FILE_ID "47722782") - set(UNCRUSTIFY_DOWNLOAD_SHA256 - "2505c9397c0a6f96c16570044bd21ea7992f27118cc1f2e3d7a6945b8e2a4702" - ) -else() - message(WARNING "No prebuilt Uncrustify library for this platform.") - return() -endif() - -string(CONCAT UNCRUSTIFY_DOWNLOAD_URL - "https://gitlab.com/Distributed-Compute-Protocol/uncrustify-build/-/" - "package_files/${UNCRUSTIFY_DOWNLOAD_PACKAGE_FILE_ID}/download" -) - -include("${CMAKE_ROOT}/Modules/FetchContent.cmake") - -FetchContent_Declare(Uncrustify - DOWNLOAD_EXTRACT_TIMESTAMP FALSE - PREFIX "${CMAKE_CURRENT_BINARY_DIR}" - SOURCE_DIR "${UNCRUSTIFY_ROOT}" - URL "${UNCRUSTIFY_DOWNLOAD_URL}" - URL_HASH "SHA256=${UNCRUSTIFY_DOWNLOAD_SHA256}" - TLS_VERIFY TRUE -) -FetchContent_MakeAvailable(Uncrustify) diff --git a/cmake/format/CMakeLists.txt b/cmake/format/CMakeLists.txt deleted file mode 100644 index 19d8c7ce..00000000 --- a/cmake/format/CMakeLists.txt +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) 2022 Distributive Inc. All Rights Reserved. - -if(NOT UNCRUSTIFY_EXECUTABLE) - if(UNCRUSTIFY_ROOT STREQUAL "") - message(STATUS "Target '${PROJECT_NAME}-format-cpp' not added:" - " UNCRUSTIFY_ROOT not set" - ) - return() - endif() - - find_program(UNCRUSTIFY_EXECUTABLE - NAMES "uncrustify" - DOC "Uncrustify executable path" - PATHS "${UNCRUSTIFY_ROOT}" - PATH_SUFFIXES "bin" - NO_DEFAULT_PATH - ) - if(NOT UNCRUSTIFY_EXECUTABLE) - message(STATUS "Target '${PROJECT_NAME}-format-cpp' not added:" - " Uncrustify not found" - ) - return() - endif() -endif() -message(STATUS "Using Uncrustify: ${UNCRUSTIFY_EXECUTABLE}") - -option(PYTHONMONKEY_CPP_FORMAT_FIX "Automatically fix formatting errors." ON) -if(PYTHONMONKEY_CPP_FORMAT_FIX) - message(STATUS "Automatically fixing C++ formatting errors") - set(PYTHONMONKEY_CPP_FORMAT_OPTIONS "--replace" "--if-changed" "--no-backup") - set(PYTHONMONKEY_CPP_FORMAT_COMMENT "Checking and fixing code formatting...") -else() - message(STATUS "Reporting C++ formatting errors without fixing") - set(PYTHONMONKEY_CPP_FORMAT_OPTIONS "--check") - string(CONCAT PYTHONMONKEY_CPP_FORMAT_COMMENT "Checking code formatting" - " (regenerate with PYTHONMONKEY_CPP_FORMAT_FIX=ON to automatically fix errors)..." - ) -endif() - -file(GLOB_RECURSE PYTHONMONKEY_CPP_FORMAT_FILES CONFIGURE_DEPENDS - "${PROJECT_SOURCE_DIR}/include/*.hh" - "${PROJECT_SOURCE_DIR}/src/*.cc" -) - -set(PYTHONMONKEY_CPP_FORMAT_COMMAND - "${UNCRUSTIFY_EXECUTABLE}" "-c" "uncrustify.cfg" - ${PYTHONMONKEY_CPP_FORMAT_OPTIONS} - ${PYTHONMONKEY_CPP_FORMAT_FILES} -) - -add_custom_target("${PROJECT_NAME}-format-cpp" ALL - COMMAND ${PYTHONMONKEY_CPP_FORMAT_COMMAND} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "${PYTHONMONKEY_CPP_FORMAT_COMMENT}" - VERBATIM - SOURCES "uncrustify.cfg" -) -# pythonmonkey_target_initialize("${PROJECT_NAME}-format-cpp") - -if(NOT AUTOPEP8_SCRIPT) - if(AUTOPEP8_ROOT STREQUAL "") - message(STATUS "Target '${PROJECT_NAME}-format-python' not added:" - " AUTOPEP8_ROOT not set" - ) - return() - endif() - - find_program(AUTOPEP8_SCRIPT - NAMES "autopep8.py" - DOC "autopep8 script path" - PATHS "${AUTOPEP8_ROOT}" - NO_DEFAULT_PATH - ) - if(NOT AUTOPEP8_SCRIPT) - message(STATUS "Target '${PROJECT_NAME}-format-python' not added:" - " autopep8 not found" - ) - return() - endif() -endif() -message(STATUS "Using autopep8: ${AUTOPEP8_SCRIPT}") - -option(PYTHONMONKEY_PYTHON_FORMAT_FIX "Automatically fix formatting errors." ON) -if(PYTHONMONKEY_PYTHON_FORMAT_FIX) - message(STATUS "Automatically fixing Python formatting errors") - set(PYTHONMONKEY_PYTHON_FORMAT_OPTIONS "--in-place" "--verbose" "--aggressive" "--aggressive") - set(PYTHONMONKEY_PYTHON_FORMAT_COMMENT "Checking and fixing code formatting...") -else() - message(STATUS "Reporting Python formatting errors without fixing") - set(PYTHONMONKEY_PYTHON_FORMAT_OPTIONS "--diff" "--verbose" "--aggressive" "--aggressive") - string(CONCAT PYTHONMONKEY_PYTHON_FORMAT_COMMENT "Checking code formatting" - " (regenerate with PYTHONMONKEY_PYTHON_FORMAT_FIX=ON to automatically fix errors)..." - ) -endif() - -file(GLOB_RECURSE PYTHONMONKEY_PYTHON_FORMAT_FILES CONFIGURE_DEPENDS - "${PROJECT_SOURCE_DIR}/python/*.py" - "${PROJECT_SOURCE_DIR}/tests/python/*.py" -) - -set(PYTHONMONKEY_PYTHON_FORMAT_COMMAND - "python3 ${AUTOPEP8_SCRIPT}" - ${PYTHONMONKEY_PYTHON_FORMAT_OPTIONS} - ${PYTHONMONKEY_PYTHON_FORMAT_FILES} -) - -execute_process( - COMMAND ${PYTHONMONKEY_PYTHON_FORMAT_COMMAND} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" -) \ No newline at end of file diff --git a/cmake/modules/FindSpiderMonkey.cmake b/cmake/modules/FindSpiderMonkey.cmake index 0db4d2dd..ab5ae2a2 100644 --- a/cmake/modules/FindSpiderMonkey.cmake +++ b/cmake/modules/FindSpiderMonkey.cmake @@ -93,8 +93,8 @@ check_cxx_source_compiles( "#include int main() { - JSRuntime *rt = JS_NewRuntime(8L * 1024L * 1024L); - if (rt != NULL) + JSContext *cx = JS_NewContext(JS::DefaultHeapMaxBytes); + if (cx != NULL) { return 0; } diff --git a/examples/use-python-module.py b/examples/use-python-module.py index 3d1b8f65..e493a014 100644 --- a/examples/use-python-module.py +++ b/examples/use-python-module.py @@ -5,4 +5,4 @@ import pythonmonkey as pm -pm.require('./use-python-module'); +pm.require('./use-python-module') diff --git a/examples/use-python-module/my-python-module.py b/examples/use-python-module/my-python-module.py index 236b873d..c0f02f21 100644 --- a/examples/use-python-module/my-python-module.py +++ b/examples/use-python-module/my-python-module.py @@ -1,5 +1,5 @@ def helloWorld(): print('hello, world!') -exports['helloWorld'] = helloWorld +exports['helloWorld'] = helloWorld diff --git a/examples/use-require.py b/examples/use-require.py index d986766e..254209c0 100644 --- a/examples/use-require.py +++ b/examples/use-require.py @@ -5,6 +5,5 @@ import pythonmonkey as pm -pm.require('./use-require/test1'); +pm.require('./use-require/test1') print("Done") - diff --git a/examples/use-require/test2.js b/examples/use-require/test2.js index 92076222..e39780f1 100644 --- a/examples/use-require/test2.js +++ b/examples/use-require/test2.js @@ -1,9 +1,9 @@ -'use strict' +'use strict'; exports.makeOutput = function makeOutput() { const argv = Array.from(arguments); argv.unshift('TEST OUTPUT: '); python.print.apply(null, argv); -} +}; diff --git a/githooks/pre-commit b/githooks/pre-commit new file mode 100755 index 00000000..fe574f22 --- /dev/null +++ b/githooks/pre-commit @@ -0,0 +1,129 @@ +#!/bin/bash +# +# @file pre-commit +# This hook script lints all code with Uncrustify, ESLint, and autopep8 on +# what is about to be commited. Called by "git commit". If this script +# exits with a non-zero status nothing will be committed. +# +# To disable checks on C++ files, do "git config hooks.nocclinting true", +# and "git config hooks.nocclinting false" to re-enable checks. +# +# To disable checks on JavaScript files, do "git config hooks.nojslinting true", +# and "git config hooks.nojslinting false" to re-enable checks. +# +# To disable checks on Python files, do "git config hooks.nopylinting true", +# and "git config hooks.pylinting false" to re-enable checks. +# +# To commit without this hook running at all, do "git commit --no-verify" +# +# @author Caleb Aikens, caleb@distributive.network +# @date Apr 2024 + +nocclinting=$(git config --type=bool hooks.nocclinting) +nojslinting=$(git config --type=bool hooks.nojslinting) +nopylinting=$(git config --type=bool hooks.nopylinting) + +RED="\e[31m" +YELLOW="\e[33m" +ENDCOLOUR="\e[0m" + +UNCRUSTIFY_SUCCESS=true +if [ "$nocclinting" != "true" ]; then + echo "linting C++ files ..." + for ccFile in `git diff --cached --name-only --diff-filter=ACM | grep -E '.cc$|.hh$' | cat`; + do + ./uncrustify --check -c uncrustify.cfg "${ccFile}" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + UNCRUSTIFY_SUCCESS=false + echo -e "uncrustify failed on: ${RED}${ccFile}${ENDCOLOUR}" + fi + done + echo "finished linting C++ files." +fi + +if [ "$UNCRUSTIFY_SUCCESS" = false ]; then +echo -e "$( +cat <'. +This will output the fixed file in the same directory with a .uncrustify extension. +You can automatically fix the file with './uncrustify -c uncrustify.cfg --replace --no-backup ' +Alternatively, you can temporarily disable uncrustify linting in a C++ file with '/* *INDENT-OFF* */' +and re-enable it with '/* *INDENT-ON* */'. +If you know what you are doing you can disable this check using: + + git config hooks.nocclinting true +${ENDCOLOUR} +EOF +)" +fi + +ESLINT_SUCCESS=true +if [ "$nojslinting" != "true" ]; then + echo "linting JavaScript files ..." + for jsFile in `git diff --cached --name-only --diff-filter=ACM | grep -E '.js$|.simple$' | cat`; + do + ESLINT_USE_FLAT_CONFIG=false eslint --max-warnings=1 "${jsFile}" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + ESLINT_SUCCESS=false + echo -e "eslint failed on: ${RED}${jsFile}${ENDCOLOUR}" + fi + done + echo "finished linting JavaScript files." +fi + +if [ "$ESLINT_SUCCESS" = false ]; then +echo -e "$( +cat <'. +Most issues can be automatically fixed with 'eslint --fix '. +NOTE: If using v9.0.0 of eslint or greater, you will have to set the ESLINT_USE_FLAT_CONFIG +environment variable to false. +Alternatively, you can temporarily disable eslint linting in a js file with '/* eslint-disable */' +and re-enable it with '/* eslint-enable */'. +If you know what you are doing you can disable this check using: + + git config hooks.nojslinting true +${ENDCOLOUR} +EOF +)" +fi + +AUTOPEP8_SUCCESS=true +if [ "$nopylinting" != "true" ]; then + echo "linting python files ..." + for pythonFile in `git diff --cached --name-only --diff-filter=ACM | grep -E '.py$|.pyi$' | cat`; + do + autopep8 "${pythonFile}" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + AUTOPEP8_SUCCESS=false + echo -e "autopep8 failed on: ${RED}${pythonFile}${ENDCOLOUR}" + fi + done + echo "finished linting python files." +fi + +if [ "$AUTOPEP8_SUCCESS" = false ]; then +echo -e "$( +cat <'. +Most issues can be automatically fixed with 'autopep8 -i '. +A description of each warning code can be seen with 'autopep8 --list-fixes', +or at: https://pep8.readthedocs.io/en/release-1.7.x/intro.html +Alternatively, you can temporarily disable autopep8 linting in a python file with '# autopep8: off' +and re-enable it with '# autopep8: on'. +If you know what you are doing you can disable this check using: + + git config hooks.nopylinting true +${ENDCOLOUR} +EOF +)" +fi + +if [ "$UNCRUSTIFY_SUCCESS" = false ] ||[ "$ESLINT_SUCCESS" = false ] || [ "$AUTOPEP8_SUCCESS" = false ]; then + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/include/BufferType.hh b/include/BufferType.hh index c3d31385..9c40d72e 100644 --- a/include/BufferType.hh +++ b/include/BufferType.hh @@ -33,6 +33,7 @@ public: * The subtype (Uint8Array, Float64Array, ...) is automatically determined by the Python buffer's [format](https://docs.python.org/3.9/c-api/buffer.html#c.Py_buffer.format) * * @param cx - javascript context pointer + * @param pyObject - the object to be converted */ static JSObject *toJsTypedArray(JSContext *cx, PyObject *pyObject); diff --git a/include/DateType.hh b/include/DateType.hh index fef94189..98ae9bfe 100644 --- a/include/DateType.hh +++ b/include/DateType.hh @@ -30,6 +30,7 @@ public: * @brief Convert a Python datetime object to JS Date * * @param cx - javascript context pointer + * @param pyObject - the python datetime object to be converted */ static JSObject *toJsDate(JSContext *cx, PyObject *pyObject); }; diff --git a/include/IntType.hh b/include/IntType.hh index 7b5e4bc5..8db78f8a 100644 --- a/include/IntType.hh +++ b/include/IntType.hh @@ -31,9 +31,10 @@ public: static PyObject *getPyObject(JSContext *cx, JS::BigInt *bigint); /** - * @brief Convert the IntType object to a JS::BigInt + * @brief Convert an int object to a JS::BigInt * * @param cx - javascript context pointer + * @param pyObject - the int object to be converted */ static JS::BigInt *toJsBigInt(JSContext *cx, PyObject *pyObject); }; diff --git a/include/JSArrayIterProxy.hh b/include/JSArrayIterProxy.hh index 57df013e..f0ca0683 100644 --- a/include/JSArrayIterProxy.hh +++ b/include/JSArrayIterProxy.hh @@ -51,7 +51,7 @@ public: * @brief .tp_traverse method * * @param self - The JSArrayIterProxy - * @param visitproc - The function to be applied on each element of the list + * @param visit - The function to be applied on each element of the list * @param arg - The argument to the visit function * @return 0 on success */ diff --git a/include/JSArrayProxy.hh b/include/JSArrayProxy.hh index a597df79..6cf98ea0 100644 --- a/include/JSArrayProxy.hh +++ b/include/JSArrayProxy.hh @@ -195,7 +195,7 @@ public: * @brief extend method * * @param self - The JSArrayProxy - * @param value - The value to be appended + * @param iterable - The value to be appended * @return PyObject* NULL on exception, None otherwise */ static PyObject *JSArrayProxy_extend(JSArrayProxy *self, PyObject *iterable); @@ -250,8 +250,9 @@ public: * @brief sort method sort in place * * @param self - The JSArrayProxy - * @param args - arguments to the sort method + * @param args - arguments to the sort method (not used) * @param nargs - number of arguments to the sort method + * @param kwnames - keyword arguments to the sort method (reverse=True|False, key=keyfunction) * @return PyObject* NULL on exception, None otherwise */ static PyObject *JSArrayProxy_sort(JSArrayProxy *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames); @@ -260,6 +261,8 @@ public: * @brief tp_traverse * * @param self - The JSArrayProxy + * @param visit - The function to be applied on each element of the list + * @param arg - The argument to the visit function * @return 0 on success */ static int JSArrayProxy_traverse(JSArrayProxy *self, visitproc visit, void *arg); diff --git a/include/JSObjectItemsProxy.hh b/include/JSObjectItemsProxy.hh index 2c740e34..f71e297e 100644 --- a/include/JSObjectItemsProxy.hh +++ b/include/JSObjectItemsProxy.hh @@ -41,7 +41,7 @@ public: * @brief .tp_traverse method * * @param self - The JSObjectItemsProxy - * @param visitproc - The function to be applied on each element of the list + * @param visit - The function to be applied on each element of the list * @param arg - The argument to the visit function * @return 0 on success */ @@ -91,6 +91,7 @@ public: * @brief mapping method * * @param self - The JSObjectItemsProxy + * @param Py_UNUSED * @return PyObject* The resulting new dict */ static PyObject *JSObjectItemsProxy_mapping(PyObject *self, void *Py_UNUSED(ignored)); diff --git a/include/JSObjectIterProxy.hh b/include/JSObjectIterProxy.hh index 8f571011..e2246ce0 100644 --- a/include/JSObjectIterProxy.hh +++ b/include/JSObjectIterProxy.hh @@ -57,7 +57,7 @@ public: * @brief .tp_traverse method * * @param self - The JSObjectIterProxy - * @param visitproc - The function to be applied on each element of the list + * @param visit - The function to be applied on each element of the list * @param arg - The argument to the visit function * @return 0 on success */ diff --git a/include/JSObjectKeysProxy.hh b/include/JSObjectKeysProxy.hh index 6d752488..bffdb18b 100644 --- a/include/JSObjectKeysProxy.hh +++ b/include/JSObjectKeysProxy.hh @@ -41,7 +41,7 @@ public: * @brief .tp_traverse method * * @param self - The JSObjectKeysProxy - * @param visitproc - The function to be applied on each element of the list + * @param visit - The function to be applied on each element of the list * @param arg - The argument to the visit function * @return 0 on success */ @@ -120,7 +120,6 @@ public: * @brief reverse iterator method * * @param self - The JSObjectKeysProxy - * @param other - The other PyObject to be and'd, expected to be dict or JSObjectKeysProxy * @return PyObject* The resulting new dict */ static PyObject *JSObjectKeysProxy_iter_reverse(JSObjectKeysProxy *self); @@ -129,6 +128,7 @@ public: * @brief mapping method * * @param self - The JSObjectKeysProxy + * @param Py_UNUSED * @return PyObject* The resulting new dict */ static PyObject *JSObjectKeysProxy_mapping(PyObject *self, void *Py_UNUSED(ignored)); diff --git a/include/JSObjectProxy.hh b/include/JSObjectProxy.hh index b504e799..2179be80 100644 --- a/include/JSObjectProxy.hh +++ b/include/JSObjectProxy.hh @@ -199,7 +199,7 @@ public: * * @param self - The JSObjectProxy * @param args - arguments to the sort method - * @param nargs - number of arguments to the sort method + * @param kwds - keyword arguments to the sort method (key-value pairs to be updated in the dict) * @return None */ static PyObject *JSObjectProxy_update_method(JSObjectProxy *self, PyObject *args, PyObject *kwds); @@ -232,6 +232,8 @@ public: * @brief tp_traverse * * @param self - The JSObjectProxy + * @param visit - The function to be applied on each element of the object + * @param arg - The argument to the visit function * @return 0 on success */ static int JSObjectProxy_traverse(JSObjectProxy *self, visitproc visit, void *arg); diff --git a/include/JSObjectValuesProxy.hh b/include/JSObjectValuesProxy.hh index d4bbbc6f..8cb40034 100644 --- a/include/JSObjectValuesProxy.hh +++ b/include/JSObjectValuesProxy.hh @@ -41,7 +41,7 @@ public: * @brief .tp_traverse method * * @param self - The JSObjectValuesProxy - * @param visitproc - The function to be applied on each element of the list + * @param visit - The function to be applied on each element of the list * @param arg - The argument to the visit function * @return 0 on success */ @@ -100,6 +100,7 @@ public: * @brief mapping method * * @param self - The JSObjectValuesProxy + * @param Py_UNUSED * @return PyObject* The resulting new dict */ static PyObject *JSObjectValuesProxy_mapping(PyObject *self, void *Py_UNUSED(ignored)); diff --git a/include/PromiseType.hh b/include/PromiseType.hh index f9f7560f..880c2b3a 100644 --- a/include/PromiseType.hh +++ b/include/PromiseType.hh @@ -35,6 +35,7 @@ public: * @brief Convert a Python [awaitable](https://docs.python.org/3/library/asyncio-task.html#awaitables) object to JS Promise * * @param cx - javascript context pointer + * @param pyObject - the python awaitable to be converted */ static JSObject *toJsPromise(JSContext *cx, PyObject *pyObject); }; diff --git a/include/PyDictProxyHandler.hh b/include/PyDictProxyHandler.hh index 0e455fc7..cb608660 100644 --- a/include/PyDictProxyHandler.hh +++ b/include/PyDictProxyHandler.hh @@ -77,9 +77,7 @@ public: * * @param cx - pointer to JSContext * @param proxy - The proxy object who's keys we output - * @param props - out-parameter of object IDsoverride; - - /** + * @param props - out-parameter of object IDs * @return true - call succeeded * @return false - call failed and an exception has been raised */ diff --git a/pyproject.toml b/pyproject.toml index 575d373f..4a9f15b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ include = [ [tool.poetry.dependencies] python = "^3.8" pyreadline3 = { version = "^3.4.1", platform = "win32" } -aiohttp = { version = "^3.9.3", extras = ["speedups"] } +aiohttp = { version = "^3.9.5", extras = ["speedups"] } pminit = { version = "*", allow-prereleases = true } @@ -66,3 +66,11 @@ pminit = { path = "./python/pminit", develop = true } [build-system] requires = ["poetry-core>=1.1.1", "poetry-dynamic-versioning==1.1.1"] build-backend = "poetry_dynamic_versioning.backend" + +[tool.autopep8] +max_line_length=120 +ignore="E111,E114,E121" # allow 2-space indents +verbose=true +indent-size=2 +aggressive=3 +exit-code=true \ No newline at end of file diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/python/pythonmonkey/__init__.py b/python/pythonmonkey/__init__.py index dafd1931..957713ff 100644 --- a/python/pythonmonkey/__init__.py +++ b/python/pythonmonkey/__init__.py @@ -5,13 +5,13 @@ # Expose the package version import importlib.metadata -__version__= importlib.metadata.version(__name__) +__version__ = importlib.metadata.version(__name__) del importlib # Load the module by default to expose global APIs -## builtin_modules +# builtin_modules require("console") require("base64") require("timers") require("url") -require("XMLHttpRequest") \ No newline at end of file +require("XMLHttpRequest") diff --git a/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py b/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py index 9956c2fb..19c5a0d6 100644 --- a/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py +++ b/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py @@ -12,17 +12,19 @@ import pythonmonkey as pm from typing import Union, ByteString, Callable, TypedDict + class XHRResponse(TypedDict, total=True): - """ - See definitions in `XMLHttpRequest-internal.d.ts` - """ - url: str - status: int - statusText: str - contentLength: int - getResponseHeader: Callable[[str], Union[str, None]] - getAllResponseHeaders: Callable[[], str] - abort: Callable[[], None] + """ + See definitions in `XMLHttpRequest-internal.d.ts` + """ + url: str + status: int + statusText: str + contentLength: int + getResponseHeader: Callable[[str], Union[str, None]] + getAllResponseHeaders: Callable[[], str] + abort: Callable[[], None] + async def request( method: str, @@ -42,82 +44,87 @@ async def request( onNetworkError: Callable[[aiohttp.ClientError], None], / ): - debug = pm.bootstrap.require("debug"); - - class BytesPayloadWithProgress(aiohttp.BytesPayload): - _chunkMaxLength = 2**16 # aiohttp default - - async def write(self, writer) -> None: - debug('xhr:io')('begin chunked write') - buf = io.BytesIO(self._value) - chunk = buf.read(self._chunkMaxLength) - while chunk: - debug('xhr:io')(' writing', len(chunk), 'bytes') - await writer.write(chunk) - processRequestBodyChunkLength(len(chunk)) - chunk = buf.read(self._chunkMaxLength) - processRequestEndOfBody() - debug('xhr:io')('finish chunked write') - - if isinstance(body, str): - body = bytes(body, "utf-8") - - # set default headers - headers.setdefault("user-agent", f"Python/{platform.python_version()} PythonMonkey/{pm.__version__}") - debug('xhr:headers')('after set default\n', headers) - - if timeoutMs > 0: - timeoutOptions = aiohttp.ClientTimeout(total=timeoutMs/1000) # convert to seconds - else: - timeoutOptions = aiohttp.ClientTimeout() # default timeout - - try: - debug('xhr:aiohttp')('creating request for', url) - async with aiohttp.request(method=method, - url=yarl.URL(url, encoded=True), - headers=headers, - data=BytesPayloadWithProgress(body) if body else None, - timeout=timeoutOptions, - ) as res: - debug('xhr:aiohttp')('got', res.content_type, 'result') - def getResponseHeader(name: str): - return res.headers.get(name) - def getAllResponseHeaders(): - headers = [] - for name, value in res.headers.items(): - headers.append(f"{name.lower()}: {value}") - headers.sort() - return "\r\n".join(headers) - def abort(): - debug('xhr:io')('abort') - res.close() - - # readyState HEADERS_RECEIVED - processResponse({ - 'url': str(res.real_url), - 'status': res.status, - 'statusText': str(res.reason or ''), - - 'getResponseHeader': getResponseHeader, - 'getAllResponseHeaders': getAllResponseHeaders, - 'abort': abort, - 'contentLength': res.content_length or 0, - }) - - async for data in res.content.iter_any(): - processBodyChunk(bytearray(data)) # PythonMonkey only accepts the mutable bytearray type - # readyState DONE - processEndOfBody() - except asyncio.TimeoutError as e: - onTimeoutError(e) - raise # rethrow - except aiohttp.ClientError as e: - onNetworkError(e) - raise # rethrow - -def decodeStr(data: bytes, encoding='utf-8'): # XXX: Remove this once we get proper TextDecoder support - return str(data, encoding=encoding) + debug = pm.bootstrap.require("debug") + + class BytesPayloadWithProgress(aiohttp.BytesPayload): + _chunkMaxLength = 2**16 # aiohttp default + + async def write(self, writer) -> None: + debug('xhr:io')('begin chunked write') + buf = io.BytesIO(self._value) + chunk = buf.read(self._chunkMaxLength) + while chunk: + debug('xhr:io')(' writing', len(chunk), 'bytes') + await writer.write(chunk) + processRequestBodyChunkLength(len(chunk)) + chunk = buf.read(self._chunkMaxLength) + processRequestEndOfBody() + debug('xhr:io')('finish chunked write') + + if isinstance(body, str): + body = bytes(body, "utf-8") + + # set default headers + headers.setdefault("user-agent", f"Python/{platform.python_version()} PythonMonkey/{pm.__version__}") + debug('xhr:headers')('after set default\n', headers) + + if timeoutMs > 0: + timeoutOptions = aiohttp.ClientTimeout(total=timeoutMs / 1000) # convert to seconds + else: + timeoutOptions = aiohttp.ClientTimeout() # default timeout + + try: + debug('xhr:aiohttp')('creating request for', url) + async with aiohttp.request(method=method, + url=yarl.URL(url, encoded=True), + headers=headers, + data=BytesPayloadWithProgress(body) if body else None, + timeout=timeoutOptions, + ) as res: + debug('xhr:aiohttp')('got', res.content_type, 'result') + + def getResponseHeader(name: str): + return res.headers.get(name) + + def getAllResponseHeaders(): + headers = [] + for name, value in res.headers.items(): + headers.append(f"{name.lower()}: {value}") + headers.sort() + return "\r\n".join(headers) + + def abort(): + debug('xhr:io')('abort') + res.close() + + # readyState HEADERS_RECEIVED + processResponse({ + 'url': str(res.real_url), + 'status': res.status, + 'statusText': str(res.reason or ''), + + 'getResponseHeader': getResponseHeader, + 'getAllResponseHeaders': getAllResponseHeaders, + 'abort': abort, + 'contentLength': res.content_length or 0, + }) + + async for data in res.content.iter_any(): + processBodyChunk(bytearray(data)) # PythonMonkey only accepts the mutable bytearray type + # readyState DONE + processEndOfBody() + except asyncio.TimeoutError as e: + onTimeoutError(e) + raise # rethrow + except aiohttp.ClientError as e: + onNetworkError(e) + raise # rethrow + + +def decodeStr(data: bytes, encoding='utf-8'): # XXX: Remove this once we get proper TextDecoder support + return str(data, encoding=encoding) + # Module exports -exports['request'] = request # type: ignore -exports['decodeStr'] = decodeStr # type: ignore +exports['request'] = request # type: ignore +exports['decodeStr'] = decodeStr # type: ignore diff --git a/python/pythonmonkey/builtin_modules/base64.py b/python/pythonmonkey/builtin_modules/base64.py index 7239911b..12ca25a0 100644 --- a/python/pythonmonkey/builtin_modules/base64.py +++ b/python/pythonmonkey/builtin_modules/base64.py @@ -3,13 +3,19 @@ # @date July 2023 # @copyright Copyright (c) 2023 Distributive Corp. +import pythonmonkey as pm import base64 -atob = lambda b64: str(base64.standard_b64decode(b64), 'latin1') -btoa = lambda data: str(base64.standard_b64encode(bytes(data, 'latin1')), 'latin1') + +def atob(b64): + return str(base64.standard_b64decode(b64), 'latin1') + + +def btoa(data): + return str(base64.standard_b64encode(bytes(data, 'latin1')), 'latin1') + # Make `atob`/`btoa` globally available -import pythonmonkey as pm pm.eval(r"""(atob, btoa) => { if (!globalThis.atob) { globalThis.atob = atob; @@ -20,5 +26,5 @@ }""")(atob, btoa) # Module exports -exports['atob'] = atob # type: ignore -exports['btoa'] = btoa # type: ignore +exports['atob'] = atob # type: ignore +exports['btoa'] = btoa # type: ignore diff --git a/python/pythonmonkey/builtin_modules/internal-binding.py b/python/pythonmonkey/builtin_modules/internal-binding.py index d9003662..b4231a2b 100644 --- a/python/pythonmonkey/builtin_modules/internal-binding.py +++ b/python/pythonmonkey/builtin_modules/internal-binding.py @@ -2,9 +2,9 @@ Re-export `internalBinding` to JS """ -import pythonmonkey as pm +import pythonmonkey as pm """ See function declarations in ./internal-binding.d.ts """ -exports = pm.internalBinding # type: ignore +exports = pm.internalBinding # type: ignore diff --git a/python/pythonmonkey/builtin_modules/util.js b/python/pythonmonkey/builtin_modules/util.js index 7ad4f6cd..91aba2f5 100644 --- a/python/pythonmonkey/builtin_modules/util.js +++ b/python/pythonmonkey/builtin_modules/util.js @@ -9,7 +9,7 @@ * @copyright Copyright (c) 2023 Distributive Corp. */ -const internalBinding = require("internal-binding") +const internalBinding = require('internal-binding'); const { isAnyArrayBuffer, @@ -18,14 +18,15 @@ const { isTypedArray, getPromiseDetails, getProxyDetails, -} = internalBinding("utils") +} = internalBinding('utils'); -const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom') +const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); // Keep this in sync with both SpiderMonkey and V8 /** @type {PromiseState.Pending} */ const kPending = 0; /** @type {PromiseState.Fulfilled} */ +// eslint-disable-next-line no-unused-vars const kFulfilled = 1; /** @type {PromiseState.Rejected} */ const kRejected = 2; @@ -34,42 +35,48 @@ const kRejected = 2; * @param {string[]} output * @param {string} separator */ -function join(output, separator) { - return output.join(separator) +function join(output, separator) +{ + return output.join(separator); } /** * @return {o is Error} */ -function isError(e) { +function isError(e) +{ return objectToString(e) === '[object Error]' || e instanceof Error; } /** * @return {o is Date} */ -function isDate(o) { +function isDate(o) +{ return objectToString(o) === '[object Date]' || o instanceof Date; } /** * @return {o is Set} */ -function isSet(o) { +function isSet(o) +{ return objectToString(o) === '[object Set]' || o instanceof Set; } /** * @return {o is Map} */ -function isMap(o) { +function isMap(o) +{ return objectToString(o) === '[object Map]' || o instanceof Map; } /** * @return {o is DataView} */ -function isDataView(o) { +function isDataView(o) +{ return objectToString(o) === '[object DataView]' || o instanceof DataView; } @@ -77,21 +84,26 @@ function isDataView(o) { * V8 IsJSExternalObject */ // TODO (Tom Tang): What's the equivalent in SpiderMonkey? -function isExternal(o) { +function isExternal(o) +{ return false; } -function objectToString(o) { +function objectToString(o) +{ return Object.prototype.toString.call(o); } // https://github.com/nodejs/node/blob/v8.17.0/lib/internal/util.js#L189-L202 -function getConstructorOf(obj) { - while (obj) { - var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor'); - if (descriptor !== undefined && - typeof descriptor.value === 'function' && - descriptor.value.name !== '') { +function getConstructorOf(obj) +{ + while (obj) + { + let descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor'); + if (descriptor !== undefined + && typeof descriptor.value === 'function' + && descriptor.value.name !== '') + { return descriptor.value; } @@ -101,7 +113,7 @@ function getConstructorOf(obj) { return null; } -/*! Start verbatim Node.js +/* ! Start verbatim Node.js * https://github.com/nodejs/node/blob/v8.17.0/lib/util.js#L59-L852 */ const inspectDefaultOptions = Object.seal({ @@ -119,20 +131,21 @@ const regExpToString = RegExp.prototype.toString; const dateToISOString = Date.prototype.toISOString; /** Return String(val) surrounded by appropriate ANSI escape codes to change the console text colour. */ +// eslint-disable-next-line no-unused-vars function colour(colourCode, val) { const esc=String.fromCharCode(27); - return `${esc}[${colourCode}m${val}${esc}[0m` + return `${esc}[${colourCode}m${val}${esc}[0m`; } -var CIRCULAR_ERROR_MESSAGE; +let CIRCULAR_ERROR_MESSAGE; /* eslint-disable */ const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c]/; const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c]/g; +const colorRegExp = /\u001b\[\d\d?m/g; /* eslint-enable */ const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/; -const colorRegExp = /\u001b\[\d\d?m/g; const numberRegExp = /^(0|[1-9][0-9]*)$/; // Escaped special characters. Use empty strings to fill up unused entries. @@ -156,43 +169,61 @@ const escapeFn = (str) => meta[str.charCodeAt(0)]; // Escape control characters, single quotes and the backslash. // This is similar to JSON stringify escaping. -function strEscape(str) { +function strEscape(str) +{ // Some magic numbers that worked out fine while benchmarking with v8 6.0 if (str.length < 5000 && !strEscapeSequencesRegExp.test(str)) return `'${str}'`; if (str.length > 100) return `'${str.replace(strEscapeSequencesReplacer, escapeFn)}'`; - var result = ''; - var last = 0; - for (var i = 0; i < str.length; i++) { + let result = ''; + let last = 0; + let i; + for (i = 0; i < str.length; i++) + { const point = str.charCodeAt(i); - if (point === 39 || point === 92 || point < 32) { - if (last === i) { + if (point === 39 || point === 92 || point < 32) + { + if (last === i) + { result += meta[point]; - } else { + } + else + { result += `${str.slice(last, i)}${meta[point]}`; } last = i + 1; } } - if (last === 0) { + if (last === 0) + { result = str; - } else if (last !== i) { + } + else if (last !== i) + { result += str.slice(last); } return `'${result}'`; } -function tryStringify(arg) { - try { +function tryStringify(arg) +{ + try + { return JSON.stringify(arg); - } catch (err) { + } + catch (err) + { // Populate the circular error message lazily - if (!CIRCULAR_ERROR_MESSAGE) { - try { + if (!CIRCULAR_ERROR_MESSAGE) + { + try + { const a = {}; a.a = a; JSON.stringify(a); - } catch (err) { - CIRCULAR_ERROR_MESSAGE = err.message; + } + catch (err2) + { + CIRCULAR_ERROR_MESSAGE = err2.message; } } if (err.name === 'TypeError' && err.message === CIRCULAR_ERROR_MESSAGE) @@ -201,12 +232,15 @@ function tryStringify(arg) { } } -function format(f) { - var i, tempStr; - if (typeof f !== 'string') { +function format(f) +{ + let i, tempStr; + if (typeof f !== 'string') + { if (arguments.length === 0) return ''; - var res = ''; - for (i = 0; i < arguments.length - 1; i++) { + let res = ''; + for (i = 0; i < arguments.length - 1; i++) + { res += inspect(arguments[i]); res += ' '; } @@ -216,14 +250,18 @@ function format(f) { if (arguments.length === 1) return f; - var str = ''; - var a = 1; - var lastPos = 0; - for (i = 0; i < f.length - 1; i++) { - if (f.charCodeAt(i) === 37) { // '%' + let str = ''; + let a = 1; + let lastPos = 0; + for (i = 0; i < f.length - 1; i++) + { + if (f.charCodeAt(i) === 37) + { // '%' const nextChar = f.charCodeAt(++i); - if (a !== arguments.length) { - switch (nextChar) { + if (a !== arguments.length) + { + switch (nextChar) + { case 115: // 's' tempStr = String(arguments[a++]); break; @@ -241,7 +279,7 @@ function format(f) { { showHidden: true, depth: 4, showProxy: true }); break; case 105: // 'i' - tempStr = `${parseInt(arguments[a++])}`; + tempStr = `${parseInt(arguments[a++], 10)}`; break; case 102: // 'f' tempStr = `${parseFloat(arguments[a++])}`; @@ -257,7 +295,9 @@ function format(f) { str += f.slice(lastPos, i - 1); str += tempStr; lastPos = i + 1; - } else if (nextChar === 37) { + } + else if (nextChar === 37) + { str += f.slice(lastPos, i); lastPos = i + 1; } @@ -267,11 +307,15 @@ function format(f) { str = f; else if (lastPos < f.length) str += f.slice(lastPos); - while (a < arguments.length) { + while (a < arguments.length) + { const x = arguments[a++]; - if ((typeof x !== 'object' && typeof x !== 'symbol') || x === null) { + if ((typeof x !== 'object' && typeof x !== 'symbol') || x === null) + { str += ` ${x}`; - } else { + } + else + { str += ` ${inspect(x)}`; } } @@ -286,7 +330,8 @@ function format(f) { * @param {Object} opts Optional options object that alters the output. */ /* Legacy: obj, showHidden, depth, colors*/ -function inspect(obj, opts = undefined) { +function inspect(obj, opts = undefined) +{ // Default options const ctx = { seen: [], @@ -301,20 +346,27 @@ function inspect(obj, opts = undefined) { indentationLvl: 0 }; // Legacy... - if (arguments.length > 2) { - if (arguments[2] !== undefined) { + if (arguments.length > 2) + { + if (arguments[2] !== undefined) + { ctx.depth = arguments[2]; } - if (arguments.length > 3 && arguments[3] !== undefined) { + if (arguments.length > 3 && arguments[3] !== undefined) + { ctx.colors = arguments[3]; } } // Set user-specified options - if (typeof opts === 'boolean') { + if (typeof opts === 'boolean') + { ctx.showHidden = opts; - } else if (opts) { + } + else if (opts) + { const optKeys = Object.keys(opts); - for (var i = 0; i < optKeys.length; i++) { + for (let i = 0; i < optKeys.length; i++) + { ctx[optKeys[i]] = opts[optKeys[i]]; } } @@ -325,15 +377,17 @@ function inspect(obj, opts = undefined) { inspect.custom = customInspectSymbol; Object.defineProperty(inspect, 'defaultOptions', { - get() { + get() + { return inspectDefaultOptions; }, - set(options) { - if (options === null || typeof options !== 'object') { + set(options) + { + if (options === null || typeof options !== 'object') + { throw new TypeError('"options" must be an object'); } Object.assign(inspectDefaultOptions, options); - return inspectDefaultOptions; } }); @@ -369,32 +423,41 @@ inspect.styles = Object.assign(Object.create(null), { 'error2': 'grey', }); -function stylizeWithColor(str, styleType) { +function stylizeWithColor(str, styleType) +{ const style = inspect.styles[styleType]; - if (style !== undefined) { + if (style !== undefined) + { const color = inspect.colors[style]; return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; } return str; } -function stylizeNoColor(str, styleType) { +function stylizeNoColor(str, styleType) +{ return str; } -function formatValue(ctx, value, recurseTimes, ln) { +function formatValue(ctx, value, recurseTimes, ln) +{ // Primitive types cannot have properties - if (typeof value !== 'object' && typeof value !== 'function') { + if (typeof value !== 'object' && typeof value !== 'function') + { return formatPrimitive(ctx.stylize, value); } - if (value === null) { + if (value === null) + { return ctx.stylize('null', 'null'); } - if (ctx.showProxy) { + if (ctx.showProxy) + { const proxy = getProxyDetails(value); - if (proxy !== undefined) { - if (recurseTimes != null) { + if (proxy !== undefined) + { + if (recurseTimes !== null) + { if (recurseTimes < 0) return ctx.stylize('Proxy [Array]', 'special'); recurseTimes -= 1; @@ -412,20 +475,24 @@ function formatValue(ctx, value, recurseTimes, ln) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it - if (ctx.customInspect) { + if (ctx.customInspect) + { const maybeCustomInspect = value[customInspectSymbol] || value.inspect; - if (typeof maybeCustomInspect === 'function' && + if (typeof maybeCustomInspect === 'function' // Filter out the util module, its inspect function is special - maybeCustomInspect !== inspect && + && maybeCustomInspect !== inspect // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { + && !(value.constructor && value.constructor.prototype === value)) + { const ret = maybeCustomInspect.call(value, recurseTimes, ctx); // If the custom inspection method returned `this`, don't go into // infinite recursion. - if (ret !== value) { - if (typeof ret !== 'string') { + if (ret !== value) + { + if (typeof ret !== 'string') + { return formatValue(ctx, ret, recurseTimes); } return ret; @@ -433,13 +500,16 @@ function formatValue(ctx, value, recurseTimes, ln) { } } - var keys; - var symbols = Object.getOwnPropertySymbols(value); + let keys; + let symbols = Object.getOwnPropertySymbols(value); // Look up the keys of the object. - if (ctx.showHidden) { + if (ctx.showHidden) + { keys = Object.getOwnPropertyNames(value); - } else { + } + else + { keys = Object.keys(value); if (symbols.length !== 0) symbols = symbols.filter((key) => propertyIsEnumerable.call(value, key)); @@ -447,35 +517,43 @@ function formatValue(ctx, value, recurseTimes, ln) { const keyLength = keys.length + symbols.length; const constructor = getConstructorOf(value); - const ctorName = constructor && constructor.name ? - `${constructor.name} ` : ''; + const ctorName = constructor && constructor.name + ? `${constructor.name} ` : ''; - var base = ''; - var formatter = formatObject; - var braces; - var noIterator = true; - var raw; + let base = ''; + let formatter = formatObject; + let braces; + let noIterator = true; + let raw; // Iterators and the rest are split to reduce checks - if (value[Symbol.iterator]) { + if (value[Symbol.iterator]) + { noIterator = false; - if (Array.isArray(value)) { + if (Array.isArray(value)) + { // Only set the constructor for non ordinary ("Array [...]") arrays. braces = [`${ctorName === 'Array ' ? '' : ctorName}[`, ']']; if (value.length === 0 && keyLength === 0) return `${braces[0]}]`; formatter = formatArray; - } else if (isSet(value)) { + } + else if (isSet(value)) + { if (value.size === 0 && keyLength === 0) return `${ctorName}{}`; braces = [`${ctorName}{`, '}']; formatter = formatSet; - } else if (isMap(value)) { + } + else if (isMap(value)) + { if (value.size === 0 && keyLength === 0) return `${ctorName}{}`; braces = [`${ctorName}{`, '}']; formatter = formatMap; - } else if (isTypedArray(value)) { + } + else if (isTypedArray(value)) + { braces = [`${ctorName}[`, ']']; formatter = formatTypedArray; // } else if (isMapIterator(value)) { @@ -484,14 +562,20 @@ function formatValue(ctx, value, recurseTimes, ln) { // } else if (isSetIterator(value)) { // braces = ['SetIterator {', '}']; // formatter = formatCollectionIterator; - } else { + } + else + { // Check for boxed strings with valueOf() // The .valueOf() call can fail for a multitude of reasons - try { + try + { raw = value.valueOf(); - } catch (e) { /* ignore */ } + } + catch (e) + { /* ignore */ } - if (typeof raw === 'string') { + if (typeof raw === 'string') + { const formatted = formatPrimitive(stylizeNoColor, raw); if (keyLength === raw.length) return ctx.stylize(`[String: ${formatted}]`, 'string'); @@ -501,86 +585,119 @@ function formatValue(ctx, value, recurseTimes, ln) { // Make boxed primitive Strings look like such keys = keys.slice(value.length); braces = ['{', '}']; - } else { + } + else + { noIterator = true; } } } - if (noIterator) { + if (noIterator) + { braces = ['{', '}']; - if (ctorName === 'Object ') { + if (ctorName === 'Object ') + { // Object fast path if (keyLength === 0) return '{}'; - } else if (typeof value === 'function') { + } + else if (typeof value === 'function') + { const name = `${constructor.name}${value.name ? `: ${value.name}` : ''}`; if (keyLength === 0) return ctx.stylize(`[${name}]`, 'special'); base = ` [${name}]`; - } else if (isRegExp(value)) { + } + else if (isRegExp(value)) + { // Make RegExps say that they are RegExps if (keyLength === 0 || recurseTimes < 0) return ctx.stylize(regExpToString.call(value), 'regexp'); base = ` ${regExpToString.call(value)}`; - } else if (isDate(value)) { - if (keyLength === 0) { + } + else if (isDate(value)) + { + if (keyLength === 0) + { if (Number.isNaN(value.getTime())) return ctx.stylize(value.toString(), 'date'); return ctx.stylize(dateToISOString.call(value), 'date'); } // Make dates with properties first say the date base = ` ${dateToISOString.call(value)}`; - } else if (isError(value)) { + } + else if (isError(value)) + { // Make error with message first say the error if (keyLength === 0 || keys.every(k => k === 'stack')) // There's only a 'stack' property return formatError(ctx, value); keys = keys.filter(k => k !== 'stack'); // When changing the 'stack' property in SpiderMonkey, it becomes enumerable. base = ` ${formatError(ctx, value)}\n`; braces.length=0; - } else if (isAnyArrayBuffer(value)) { + } + else if (isAnyArrayBuffer(value)) + { // Fast path for ArrayBuffer and SharedArrayBuffer. // Can't do the same for DataView because it has a non-primitive // .buffer property that we need to recurse for. if (keyLength === 0) - return ctorName + - `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`; + return ctorName + + `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`; braces[0] = `${ctorName}{`; keys.unshift('byteLength'); - } else if (isDataView(value)) { + } + else if (isDataView(value)) + { braces[0] = `${ctorName}{`; // .buffer goes last, it's not a primitive like the others. keys.unshift('byteLength', 'byteOffset', 'buffer'); - } else if (isPromise(value)) { + } + else if (isPromise(value)) + { braces[0] = `${ctorName}{`; formatter = formatPromise; - } else { + } + else + { // Check boxed primitives other than string with valueOf() // NOTE: `Date` has to be checked first! // The .valueOf() call can fail for a multitude of reasons - try { + try + { raw = value.valueOf(); - } catch (e) { /* ignore */ } + } + catch (e) + { /* ignore */ } - if (typeof raw === 'number') { + if (typeof raw === 'number') + { // Make boxed primitive Numbers look like such const formatted = formatPrimitive(stylizeNoColor, raw); if (keyLength === 0) return ctx.stylize(`[Number: ${formatted}]`, 'number'); base = ` [Number: ${formatted}]`; - } else if (typeof raw === 'boolean') { + } + else if (typeof raw === 'boolean') + { // Make boxed primitive Booleans look like such const formatted = formatPrimitive(stylizeNoColor, raw); if (keyLength === 0) return ctx.stylize(`[Boolean: ${formatted}]`, 'boolean'); base = ` [Boolean: ${formatted}]`; - } else if (typeof raw === 'symbol') { + } + else if (typeof raw === 'symbol') + { const formatted = formatPrimitive(stylizeNoColor, raw); return ctx.stylize(`[Symbol: ${formatted}]`, 'symbol'); - } else if (keyLength === 0) { + } + else if (keyLength === 0) + { if (isExternal(value)) return ctx.stylize('[External]', 'special'); return `${ctorName}{}`; - } else { + } + else + { braces[0] = `${ctorName}{`; } } @@ -591,8 +708,10 @@ function formatValue(ctx, value, recurseTimes, ln) { if (ctx.seen.indexOf(value) !== -1) return ctx.stylize('[Circular]', 'special'); - if (recurseTimes != null) { - if (recurseTimes < 0) { + if (recurseTimes !== null) + { + if (recurseTimes < 0) + { if (Array.isArray(value)) return ctx.stylize('[Array]', 'special'); return ctx.stylize('[Object]', 'special'); @@ -603,7 +722,8 @@ function formatValue(ctx, value, recurseTimes, ln) { ctx.seen.push(value); const output = formatter(ctx, value, recurseTimes, keys); - for (var i = 0; i < symbols.length; i++) { + for (let i = 0; i < symbols.length; i++) + { output.push(formatProperty(ctx, value, recurseTimes, symbols[i], 0)); } ctx.seen.pop(); @@ -611,14 +731,16 @@ function formatValue(ctx, value, recurseTimes, ln) { return reduceToSingleString(ctx, output, base, braces, ln); } -function formatNumber(fn, value) { +function formatNumber(fn, value) +{ // Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0. if (Object.is(value, -0)) return fn('-0', 'number'); return fn(`${value}`, 'number'); } -function formatPrimitive(fn, value) { +function formatPrimitive(fn, value) +{ if (typeof value === 'string') return fn(strEscape(value), 'string'); if (typeof value === 'number') @@ -648,39 +770,44 @@ function formatError(ctx, error) } const stackEls = error.stack - .split('\n') - .filter(a => a.length > 0) - .map(a => ` ${a}`); - const retstr = - `${error.name}: ${error.message}\n` + .split('\n') + .filter(a => a.length > 0) + .map(a => ` ${a}`); + const retstr + = `${error.name}: ${error.message}\n` + stackEls[0] + '\n' + style(stackEls.slice(1).join('\n')); return retstr; } -function formatObject(ctx, value, recurseTimes, keys) { +function formatObject(ctx, value, recurseTimes, keys) +{ const len = keys.length; const output = new Array(len); - for (var i = 0; i < len; i++) + for (let i = 0; i < len; i++) output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0); return output; } // The array is sparse and/or has extra keys -function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) { +function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) +{ const output = []; const keyLen = keys.length; - var visibleLength = 0; - var i = 0; - if (keyLen !== 0 && numberRegExp.test(keys[0])) { - for (const key of keys) { + let visibleLength = 0; + let i = 0; + if (keyLen !== 0 && numberRegExp.test(keys[0])) + { + for (const key of keys) + { if (visibleLength === maxLength) break; - const index = +key; + const index = Number(key); // Arrays can only have up to 2^32 - 1 entries if (index > 2 ** 32 - 2) break; - if (i !== index) { + if (i !== index) + { if (!numberRegExp.test(key)) break; const emptyItems = index - i; @@ -696,7 +823,8 @@ function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) { i++; } } - if (i < valLen && visibleLength !== maxLength) { + if (i < valLen && visibleLength !== maxLength) + { const len = valLen - i; const ending = len > 1 ? 's' : ''; const message = `<${len} empty item${ending}>`; @@ -706,24 +834,31 @@ function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) { return output; } const remaining = valLen - i; - if (remaining > 0) { + if (remaining > 0) + { output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); } - if (ctx.showHidden && keys[keyLen - 1] === 'length') { + if (ctx.showHidden && keys[keyLen - 1] === 'length') + { // No extra keys output.push(formatProperty(ctx, value, recurseTimes, 'length', 2)); - } else if (valLen === 0 || - keyLen > valLen && keys[valLen - 1] === `${valLen - 1}`) { + } + else if (valLen === 0 + || keyLen > valLen && keys[valLen - 1] === `${valLen - 1}`) + { // The array is not sparse for (i = valLen; i < keyLen; i++) output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2)); - } else if (keys[keyLen - 1] !== `${valLen - 1}`) { + } + else if (keys[keyLen - 1] !== `${valLen - 1}`) + { const extra = []; // Only handle special keys - var key; - for (i = keys.length - 1; i >= 0; i--) { + let key; + for (i = keys.length - 1; i >= 0; i--) + { key = keys[i]; - if (numberRegExp.test(key) && +key < 2 ** 32 - 1) + if (numberRegExp.test(key) && Number(key) < 2 ** 32 - 1) break; extra.push(formatProperty(ctx, value, recurseTimes, key, 2)); } @@ -733,7 +868,8 @@ function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) { return output; } -function formatArray(ctx, value, recurseTimes, keys) { +function formatArray(ctx, value, recurseTimes, keys) +{ const len = Math.min(Math.max(0, ctx.maxArrayLength), value.length); const hidden = ctx.showHidden ? 1 : 0; const valLen = value.length; @@ -743,7 +879,8 @@ function formatArray(ctx, value, recurseTimes, keys) { const remaining = valLen - len; const output = new Array(len + (remaining > 0 ? 1 : 0) + hidden); - for (var i = 0; i < len; i++) + let i; + for (i = 0; i < len; i++) output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 1); if (remaining > 0) output[i++] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`; @@ -752,15 +889,18 @@ function formatArray(ctx, value, recurseTimes, keys) { return output; } -function formatTypedArray(ctx, value, recurseTimes, keys) { +function formatTypedArray(ctx, value, recurseTimes, keys) +{ const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), value.length); const remaining = value.length - maxLength; const output = new Array(maxLength + (remaining > 0 ? 1 : 0)); - for (var i = 0; i < maxLength; ++i) + let i; + for (i = 0; i < maxLength; ++i) output[i] = formatNumber(ctx.stylize, value[i]); if (remaining > 0) output[i] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`; - if (ctx.showHidden) { + if (ctx.showHidden) + { // .buffer goes last, it's not a primitive like the others. const extraKeys = [ 'BYTES_PER_ELEMENT', @@ -769,22 +909,25 @@ function formatTypedArray(ctx, value, recurseTimes, keys) { 'byteOffset', 'buffer' ]; - for (i = 0; i < extraKeys.length; i++) { + for (i = 0; i < extraKeys.length; i++) + { const str = formatValue(ctx, value[extraKeys[i]], recurseTimes); output.push(`[${extraKeys[i]}]: ${str}`); } } // TypedArrays cannot have holes. Therefore it is safe to assume that all // extra keys are indexed after value.length. - for (i = value.length; i < keys.length; i++) { + for (i = value.length; i < keys.length; i++) + { output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2)); } return output; } -function formatSet(ctx, value, recurseTimes, keys) { +function formatSet(ctx, value, recurseTimes, keys) +{ const output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0)); - var i = 0; + let i = 0; for (const v of value) output[i++] = formatValue(ctx, v, recurseTimes); // With `showHidden`, `length` will display as a hidden property for @@ -792,86 +935,119 @@ function formatSet(ctx, value, recurseTimes, keys) { // property isn't selected by Object.getOwnPropertyNames(). if (ctx.showHidden) output[i++] = `[size]: ${ctx.stylize(`${value.size}`, 'number')}`; - for (var n = 0; n < keys.length; n++) { + for (let n = 0; n < keys.length; n++) + { output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0); } return output; } -function formatMap(ctx, value, recurseTimes, keys) { +function formatMap(ctx, value, recurseTimes, keys) +{ const output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0)); - var i = 0; + let i = 0; for (const [k, v] of value) - output[i++] = `${formatValue(ctx, k, recurseTimes)} => ` + - formatValue(ctx, v, recurseTimes); + output[i++] = `${formatValue(ctx, k, recurseTimes)} => ` + + formatValue(ctx, v, recurseTimes); // See comment in formatSet if (ctx.showHidden) output[i++] = `[size]: ${ctx.stylize(`${value.size}`, 'number')}`; - for (var n = 0; n < keys.length; n++) { + for (let n = 0; n < keys.length; n++) + { output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0); } return output; } -function formatPromise(ctx, value, recurseTimes, keys) { - var output; +function formatPromise(ctx, value, recurseTimes, keys) +{ + let output; const [state, result] = getPromiseDetails(value); - if (state === kPending) { + if (state === kPending) + { output = ['']; - } else { + } + else + { const str = formatValue(ctx, result, recurseTimes); output = [state === kRejected ? ` ${str}` : str]; } - for (var n = 0; n < keys.length; n++) { + for (let n = 0; n < keys.length; n++) + { output.push(formatProperty(ctx, value, recurseTimes, keys[n], 0)); } return output; } -function formatProperty(ctx, value, recurseTimes, key, array) { - var name, str; - const desc = Object.getOwnPropertyDescriptor(value, key) || - { value: value[key], enumerable: true }; - if (desc.value !== undefined) { +function formatProperty(ctx, value, recurseTimes, key, array) +{ + let name, str; + const desc = Object.getOwnPropertyDescriptor(value, key) + || { value: value[key], enumerable: true }; + if (desc.value !== undefined) + { const diff = array === 0 ? 3 : 2; ctx.indentationLvl += diff; str = formatValue(ctx, desc.value, recurseTimes, array === 0); ctx.indentationLvl -= diff; - } else if (desc.get !== undefined) { - if (desc.set !== undefined) { + } + else if (desc.get !== undefined) + { + if (desc.set !== undefined) + { str = ctx.stylize('[Getter/Setter]', 'special'); - } else { + } + else + { str = ctx.stylize('[Getter]', 'special'); } - } else if (desc.set !== undefined) { + } + else if (desc.set !== undefined) + { str = ctx.stylize('[Setter]', 'special'); - } else { + } + else + { str = ctx.stylize('undefined', 'undefined'); } - if (array === 1) { + if (array === 1) + { return str; } - if (typeof key === 'symbol') { + if (typeof key === 'symbol') + { name = `[${ctx.stylize(key.toString(), 'symbol')}]`; - } else if (desc.enumerable === false) { + } + else if (desc.enumerable === false) + { name = `[${key}]`; - } else if (keyStrRegExp.test(key)) { + } + else if (keyStrRegExp.test(key)) + { name = ctx.stylize(key, 'name'); - } else { + } + else + { name = ctx.stylize(strEscape(key), 'string'); } return `${name}: ${str}`; } -function reduceToSingleString(ctx, output, base, braces, addLn) { +function reduceToSingleString(ctx, output, base, braces, addLn) +{ const breakLength = ctx.breakLength; - if (output.length * 2 <= breakLength) { - var length = 0; - for (var i = 0; i < output.length && length <= breakLength; i++) { - if (ctx.colors) { + if (output.length * 2 <= breakLength) + { + let length = 0; + for (let i = 0; i < output.length && length <= breakLength; i++) + { + if (ctx.colors) + { length += output[i].replace(colorRegExp, '').length + 1; - } else { + } + else + { length += output[i].length + 1; } } @@ -888,14 +1064,14 @@ function reduceToSingleString(ctx, output, base, braces, addLn) { // items will not line up correctly. const indentation = ' '.repeat(ctx.indentationLvl); const extraLn = addLn === true ? `\n${indentation}` : ''; - const ln = base === '' && braces[0].length === 1 ? - ' ' : `${base}\n${indentation} `; + const ln = base === '' && braces[0].length === 1 + ? ' ' : `${base}\n${indentation} `; const str = join(output, `,\n${indentation} `); return `${extraLn}${braces[0]}${ln}${str} ${braces[1]}`; } -/*! +/* ! * End of verbatim Node.js excerpt */ diff --git a/python/pythonmonkey/cli/pmjs.py b/python/pythonmonkey/cli/pmjs.py index b9c0a795..ec970a9a 100755 --- a/python/pythonmonkey/cli/pmjs.py +++ b/python/pythonmonkey/cli/pmjs.py @@ -4,19 +4,22 @@ # @date June 2023 # @copyright Copyright (c) 2023 Distributive Corp. -import sys, os, signal, getopt +import sys +import os +import signal +import getopt import readline import asyncio import pythonmonkey as pm from pythonmonkey.lib import pmdb globalThis = pm.eval("globalThis") -evalOpts = { 'filename': __file__, 'fromPythonFrame': True, 'strict': False } # type: pm.EvalOptions +evalOpts = {'filename': __file__, 'fromPythonFrame': True, 'strict': False} # type: pm.EvalOptions if (os.getenv('PMJS_PATH')): - requirePath = list(map(os.path.abspath, os.getenv('PMJS_PATH').split(','))) + requirePath = list(map(os.path.abspath, os.getenv('PMJS_PATH').split(','))) else: - requirePath = False; + requirePath = False pm.eval("""'use strict'; const cmds = {}; @@ -144,142 +147,143 @@ return util.inspect(error); } } -""", evalOpts); +""", evalOpts) + async def repl(): + """ + Start a REPL to evaluate JavaScript code in the extra-module environment. Multi-line statements and + readline history are supported. ^C support is sketchy. Exit the REPL with ^D or ".quit". + """ + + print('Welcome to PythonMonkey v' + pm.__version__ + '.') + print('Type ".help" for more information.') + readline.parse_and_bind('set editing-mode emacs') + histfile = os.getenv('PMJS_REPL_HISTORY') or os.path.expanduser('~/.pmjs_history') + if (os.path.exists(histfile)): + try: + readline.read_history_file(histfile) + except BaseException: + pass + + got_sigint = 0 + statement = '' + readline_skip_chars = 0 + inner_loop = False + + def save_history(): + nonlocal histfile + readline.write_history_file(histfile) + + import atexit + atexit.register(save_history) + + def quit(): """ - Start a REPL to evaluate JavaScript code in the extra-module environment. Multi-line statements and - readline history are supported. ^C support is sketchy. Exit the REPL with ^D or ".quit". + Quit the REPL. Repl saved by atexit handler. """ + globalThis.python.exit() # need for python.exit.code in require.py - print('Welcome to PythonMonkey v' + pm.__version__ +'.') - print('Type ".help" for more information.') - readline.parse_and_bind('set editing-mode emacs') - histfile = os.getenv('PMJS_REPL_HISTORY') or os.path.expanduser('~/.pmjs_history') - if (os.path.exists(histfile)): - try: - readline.read_history_file(histfile) - except: - pass - - got_sigint = 0 - statement = '' - readline_skip_chars = 0 - inner_loop = False - - def save_history(): - nonlocal histfile - readline.write_history_file(histfile) - - import atexit - atexit.register(save_history) - - def quit(): - """ - Quit the REPL. Repl saved by atexit handler. - """ - globalThis.python.exit(); # need for python.exit.code in require.py - - def sigint_handler(signum, frame): - """ - Handle ^C by aborting the entry of the current statement and quitting when double-struck. - - Sometimes this happens in the main input() function. When that happens statement is "", because - we have not yet returned from input(). Sometimes it happens in the middle of the inner loop's - input() - in that case, statement is the beginning of a multiline expression. Hitting ^C in the - middle of a multiline express cancels its input, but readline's input() doesn't return, so we - have to print the extra > prompt and fake it by later getting rid of the first readline_skip_chars - characters from the input buffer. - """ - nonlocal got_sigint - nonlocal statement - nonlocal readline_skip_chars - nonlocal inner_loop - - got_sigint = got_sigint + 1 - if (got_sigint > 1): - raise EOFError - - if (inner_loop != True): - if (got_sigint == 1 and len(readline.get_line_buffer()) == readline_skip_chars): - # First ^C with nothing in the input buffer - sys.stdout.write("\n(To exit, press Ctrl+C again or Ctrl+D or type .exit)") - elif (got_sigint == 1 and readline.get_line_buffer() != ""): - # Input buffer has text - clear it - got_sigint = 0 - readline_skip_chars = len(readline.get_line_buffer()) - else: - if (got_sigint == 1 and statement == "" and len(readline.get_line_buffer()) == readline_skip_chars): - # statement == "" means that the inner loop has already seen ^C and is now faking the outer loop - sys.stdout.write("\n(To exit, press Ctrl+C again or Ctrl+D or type .exit)") - elif (got_sigint == 1 and statement != ""): - # ^C happened on inner loop while it was still thinking we were doing a multiline-expression; since - # we can't break the input() function, we set it up to return an outer expression and fake the outer loop - got_sigint = 0 - readline_skip_chars = len(readline.get_line_buffer()) - - sys.stdout.write("\n> ") + def sigint_handler(signum, frame): + """ + Handle ^C by aborting the entry of the current statement and quitting when double-struck. + + Sometimes this happens in the main input() function. When that happens statement is "", because + we have not yet returned from input(). Sometimes it happens in the middle of the inner loop's + input() - in that case, statement is the beginning of a multiline expression. Hitting ^C in the + middle of a multiline express cancels its input, but readline's input() doesn't return, so we + have to print the extra > prompt and fake it by later getting rid of the first readline_skip_chars + characters from the input buffer. + """ + nonlocal got_sigint + nonlocal statement + nonlocal readline_skip_chars + nonlocal inner_loop + + got_sigint = got_sigint + 1 + if (got_sigint > 1): + raise EOFError + + if (not inner_loop): + if (got_sigint == 1 and len(readline.get_line_buffer()) == readline_skip_chars): + # First ^C with nothing in the input buffer + sys.stdout.write("\n(To exit, press Ctrl+C again or Ctrl+D or type .exit)") + elif (got_sigint == 1 and readline.get_line_buffer() != ""): + # Input buffer has text - clear it + got_sigint = 0 + readline_skip_chars = len(readline.get_line_buffer()) + else: + if (got_sigint == 1 and statement == "" and len(readline.get_line_buffer()) == readline_skip_chars): + # statement == "" means that the inner loop has already seen ^C and is now faking the outer loop + sys.stdout.write("\n(To exit, press Ctrl+C again or Ctrl+D or type .exit)") + elif (got_sigint == 1 and statement != ""): + # ^C happened on inner loop while it was still thinking we were doing a multiline-expression; since + # we can't break the input() function, we set it up to return an outer expression and fake the outer loop + got_sigint = 0 + readline_skip_chars = len(readline.get_line_buffer()) + + sys.stdout.write("\n> ") + statement = "" + signal.signal(signal.SIGINT, sigint_handler) + + # Main Loop + # + # Read lines entered by the user and collect them in a statement. Once the statement is a candiate + # for JavaScript evaluation (determined by pm.isCompilableUnit(), send it to replEval(). Statements + # beginning with a . are interpreted as REPL commands and sent to replCmd(). + # + # Beware - extremely tricky interplay between readline and the SIGINT handler. This is largely because we + # we can't clear the pending line buffer, so we have to fake it by re-displaying the prompt and subtracting + # characters. Another complicating factor is that the handler will suspend and resume readline, but there + # is no mechanism to force readline to return before the user presses enter. + # + while got_sigint < 2: + try: + await asyncio.sleep(0) + inner_loop = False + if (statement == ""): + statement = input('> ')[readline_skip_chars:] + readline_skip_chars = 0 + + if (len(statement) == 0): + continue + if (statement[0] == '.'): + cmd_output = globalThis.replCmd(statement[1:]) + if (cmd_output is not None): + print(cmd_output) + statement = "" + continue + if (pm.isCompilableUnit(statement)): + print(globalThis.replEval(statement)) statement = "" - signal.signal(signal.SIGINT, sigint_handler) - - # Main Loop - # - # Read lines entered by the user and collect them in a statement. Once the statement is a candiate - # for JavaScript evaluation (determined by pm.isCompilableUnit(), send it to replEval(). Statements - # beginning with a . are interpreted as REPL commands and sent to replCmd(). - # - # Beware - extremely tricky interplay between readline and the SIGINT handler. This is largely because we - # we can't clear the pending line buffer, so we have to fake it by re-displaying the prompt and subtracting - # characters. Another complicating factor is that the handler will suspend and resume readline, but there - # is no mechanism to force readline to return before the user presses enter. - # - while got_sigint < 2: - try: - await asyncio.sleep(0) - inner_loop = False - if (statement == ""): - statement = input('> ')[readline_skip_chars:] - readline_skip_chars = 0 - - if (len(statement) == 0): - continue - if (statement[0] == '.'): - cmd_output = globalThis.replCmd(statement[1:]); - if (cmd_output != None): - print(cmd_output) - statement = "" - continue - if (pm.isCompilableUnit(statement)): - print(globalThis.replEval(statement)) - statement = "" - got_sigint = 0 - else: - got_sigint = 0 - # This loop builds a multi-line statement, but if the user hits ^C during this build, we - # abort the statement. The tricky part here is that the input('... ') doesn't quit when - # SIGINT is received, so we have to patch things up so that the next-entered line is - # treated as the input at the top of the loop. - while (got_sigint == 0): - await asyncio.sleep(0) - inner_loop = True - lineBuffer = input('... ') - more = lineBuffer[readline_skip_chars:] - readline_skip_chars = 0 - if (got_sigint > 0): - statement = more - break - statement = statement + '\n' + more - if (pm.isCompilableUnit(statement)): - print(globalThis.replEval(statement)) - statement = "" - break - except EOFError: - print() - quit() + got_sigint = 0 + else: + got_sigint = 0 + # This loop builds a multi-line statement, but if the user hits ^C during this build, we + # abort the statement. The tricky part here is that the input('... ') doesn't quit when + # SIGINT is received, so we have to patch things up so that the next-entered line is + # treated as the input at the top of the loop. + while (got_sigint == 0): + await asyncio.sleep(0) + inner_loop = True + lineBuffer = input('... ') + more = lineBuffer[readline_skip_chars:] + readline_skip_chars = 0 + if (got_sigint > 0): + statement = more + break + statement = statement + '\n' + more + if (pm.isCompilableUnit(statement)): + print(globalThis.replEval(statement)) + statement = "" + break + except EOFError: + print() + quit() def usage(): - print("""Usage: pmjs [options] [ script.js ] [arguments] + print("""Usage: pmjs [options] [ script.js ] [arguments] Options: - script read from stdin (default if no file name is provided, interactive mode if a tty) @@ -297,85 +301,92 @@ def usage(): TZ specify the timezone configuration PMJS_PATH ':'-separated list of directories prefixed to the module search path PMJS_REPL_HISTORY path to the persistent REPL history file""" - ) + ) + def initGlobalThis(): - """ - Initialize globalThis for pmjs use in the extra-module context (eg -r, -e, -p). This context - needs a require function which resolves modules relative to the current working directory at pmjs - launch. The global require is to the JS function using a trick instead of a JS-wrapped-Python-wrapped function - """ - global requirePath + """ + Initialize globalThis for pmjs use in the extra-module context (eg -r, -e, -p). This context + needs a require function which resolves modules relative to the current working directory at pmjs + launch. The global require is to the JS function using a trick instead of a JS-wrapped-Python-wrapped function + """ + global requirePath + + require = pm.createRequire(os.path.abspath(os.getcwd() + '/__pmjs_virtual__'), requirePath) + globalThis.require = require + globalInitModule = require( + os.path.realpath( + os.path.dirname(__file__) + + "/../lib/pmjs/global-init")) # module load has side-effects + globalThis.arguments = sys.argv + return globalInitModule - require = pm.createRequire(os.path.abspath(os.getcwd() + '/__pmjs_virtual__'), requirePath) - globalThis.require = require - globalInitModule = require(os.path.realpath(os.path.dirname(__file__) + "/../lib/pmjs/global-init")) # module load has side-effects - globalThis.arguments = sys.argv - return globalInitModule def main(): - """ - Main program entry point - """ - enterRepl = sys.stdin.isatty() - forceRepl = False - globalInitModule = initGlobalThis() - global requirePath + """ + Main program entry point + """ + enterRepl = sys.stdin.isatty() + forceRepl = False + globalInitModule = initGlobalThis() + global requirePath + + try: + opts, args = getopt.getopt(sys.argv[1:], "hie:p:r:v", ["help", "eval=", "print=", + "require=", "version", "interactive", "use-strict", "inspect"]) + except getopt.GetoptError as err: + # print help information and exit: + print(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + output = None + verbose = False + for o, a in opts: + if o in ("-v", "--version"): + print(pm.__version__) + sys.exit() + elif o in ("--use-strict"): + evalOpts['strict'] = True + elif o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("-i", "--interactive"): + forceRepl = True + elif o in ("-e", "--eval"): + async def runEval(): + pm.eval(a, evalOpts) + await pm.wait() + asyncio.run(runEval()) + enterRepl = False + elif o in ("-p", "--print"): + async def runEvalPrint(): + ret = pm.eval(a, evalOpts) + pm.eval("ret => console.log(ret)", evalOpts)(ret) + await pm.wait() + asyncio.run(runEvalPrint()) + enterRepl = False + elif o in ("-r", "--require"): + globalThis.require(a) + elif o in ("--inspect"): + pmdb.enable() + else: + assert False, "unhandled option" + + if (len(args) > 0): + async def runJS(): + globalInitModule.patchGlobalRequire() + pm.runProgramModule(args[0], args, requirePath) + await pm.wait() # blocks until all asynchronous calls finish + asyncio.run(runJS()) + elif (enterRepl or forceRepl): + async def runREPL(): + globalInitModule.initReplLibs() + await repl() + await pm.wait() + asyncio.run(runREPL()) + + globalThis.python.exit() # need for python.exit.code in require.py - try: - opts, args = getopt.getopt(sys.argv[1:], "hie:p:r:v", ["help", "eval=", "print=", "require=", "version", "interactive", "use-strict", "inspect"]) - except getopt.GetoptError as err: - # print help information and exit: - print(err) # will print something like "option -a not recognized" - usage() - sys.exit(2) - output = None - verbose = False - for o, a in opts: - if o in ("-v", "--version"): - print(pm.__version__) - sys.exit() - elif o in ("--use-strict"): - evalOpts['strict'] = True - elif o in ("-h", "--help"): - usage() - sys.exit() - elif o in ("-i", "--interactive"): - forceRepl = True - elif o in ("-e", "--eval"): - async def runEval(): - pm.eval(a, evalOpts) - await pm.wait() - asyncio.run(runEval()) - enterRepl = False - elif o in ("-p", "--print"): - async def runEvalPrint(): - ret = pm.eval(a, evalOpts) - pm.eval("ret => console.log(ret)", evalOpts)(ret) - await pm.wait() - asyncio.run(runEvalPrint()) - enterRepl = False - elif o in ("-r", "--require"): - globalThis.require(a) - elif o in ("--inspect"): - pmdb.enable() - else: - assert False, "unhandled option" - - if (len(args) > 0): - async def runJS(): - globalInitModule.patchGlobalRequire() - pm.runProgramModule(args[0], args, requirePath) - await pm.wait() # blocks until all asynchronous calls finish - asyncio.run(runJS()) - elif (enterRepl or forceRepl): - async def runREPL(): - globalInitModule.initReplLibs() - await repl() - await pm.wait() - asyncio.run(runREPL()) - - globalThis.python.exit(); # need for python.exit.code in require.py if __name__ == "__main__": - main() + main() diff --git a/python/pythonmonkey/helpers.py b/python/pythonmonkey/helpers.py index 638a76a0..5a1c718f 100644 --- a/python/pythonmonkey/helpers.py +++ b/python/pythonmonkey/helpers.py @@ -7,29 +7,31 @@ # # @copyright Copyright (c) 2023 Distributive Corp. -from . import pythonmonkey as pm -evalOpts = { 'filename': __file__, 'fromPythonFrame': True } +from . import pythonmonkey as pm +evalOpts = {'filename': __file__, 'fromPythonFrame': True} + def typeof(jsval): - """ - typeof function - wraps JS typeof operator - """ - return pm.eval("""'use strict'; ( -function pmTypeof(jsval) + """ + typeof function - wraps JS typeof operator + """ + return pm.eval("""'use strict'; ( +function pmTypeof(jsval) { return typeof jsval; } - )""", evalOpts)(jsval); + )""", evalOpts)(jsval) + def new(ctor): - """ - new function - emits function which wraps JS new operator, emitting a lambda which constructs a new - JS object upon invocation. - """ - if (typeof(ctor) == 'string'): - ctor = pm.eval(ctor) - - newCtor = pm.eval("""'use strict'; ( + """ + new function - emits function which wraps JS new operator, emitting a lambda which constructs a new + JS object upon invocation. + """ + if (typeof(ctor) == 'string'): + ctor = pm.eval(ctor) + + newCtor = pm.eval("""'use strict'; ( function pmNewFactory(ctor) { return function newCtor(args) { @@ -38,13 +40,14 @@ def new(ctor): }; } )""", evalOpts)(ctor) - return (lambda *args: newCtor(list(args))) + return (lambda *args: newCtor(list(args))) + # List which symbols are exposed to the pythonmonkey module. -__all__ = [ "new", "typeof" ] +__all__ = ["new", "typeof"] # Add the non-enumerable properties of globalThis which don't collide with pythonmonkey.so as exports: -globalThis = pm.eval('globalThis'); +globalThis = pm.eval('globalThis') pmGlobals = vars(pm) exports = pm.eval(""" @@ -53,7 +56,7 @@ def new(ctor): """, evalOpts) for index in range(0, len(exports)): - name = exports[index] - if (pmGlobals.get(name) == None): - globals().update({name: globalThis[name]}) - __all__.append(name) + name = exports[index] + if (pmGlobals.get(name) is None): + globals().update({name: globalThis[name]}) + __all__.append(name) diff --git a/python/pythonmonkey/lib/pmdb.py b/python/pythonmonkey/lib/pmdb.py index a79fe823..9f5ffcab 100644 --- a/python/pythonmonkey/lib/pmdb.py +++ b/python/pythonmonkey/lib/pmdb.py @@ -5,19 +5,21 @@ import pythonmonkey as pm + def debuggerInput(prompt: str): try: - return input(prompt) # blocking + return input(prompt) # blocking except KeyboardInterrupt: - print("\b\bQuit") # to match the behaviour of gdb + print("\b\bQuit") # to match the behaviour of gdb return "" except Exception as e: print(e) return "" -def enable(debuggerGlobalObject = pm.eval("debuggerGlobal")): + +def enable(debuggerGlobalObject=pm.eval("debuggerGlobal")): if debuggerGlobalObject._pmdbEnabled: - return # already enabled, skipping + return # already enabled, skipping debuggerGlobalObject._pmdbEnabled = True debuggerGlobalObject.eval("""(debuggerInput, _pythonPrint, _pythonExit) => { @@ -40,19 +42,19 @@ def enable(debuggerGlobalObject = pm.eval("debuggerGlobal")): const logger = makeDebuggeeValue(mainGlobal.console.log) logger.apply(logger, args.map(makeDebuggeeValue)) } - + function printErr (...args) { const logger = makeDebuggeeValue(mainGlobal.console.error) logger.apply(logger, args.map(makeDebuggeeValue)) } - + function printSource (frame, location) { const src = frame.script.source.text const line = src.split('\\n').slice(location.lineNumber-1, location.lineNumber).join('\\n') print(line) print(" ".repeat(location.columnNumber) + "^") // indicate column position } - + function getCommandInputs () { const input = debuggerInput("(pmdb) > ") // blocking const [_, command, rest] = input.match(/\\s*(\\w+)?(?:\\s+(.*))?/) @@ -65,7 +67,7 @@ def enable(debuggerGlobalObject = pm.eval("debuggerGlobal")): // This bytecode offset does not qualify as a breakpoint, skipping return } - + blockingLoop: while (true) { const { command, rest } = getCommandInputs() // blocking switch (command) { diff --git a/python/pythonmonkey/lib/pmjs/global-init.js b/python/pythonmonkey/lib/pmjs/global-init.js index e2ac1497..d1ac6ee8 100644 --- a/python/pythonmonkey/lib/pmjs/global-init.js +++ b/python/pythonmonkey/lib/pmjs/global-init.js @@ -29,9 +29,9 @@ for (let mid in require.cache) exports.patchGlobalRequire = function pmjs$$patchGlobalRequire() { globalThis.require = require; -} +}; exports.initReplLibs = function pmjs$$initReplLibs() { globalThis.util = require('util'); -} +}; diff --git a/python/pythonmonkey/pythonmonkey.pyi b/python/pythonmonkey/pythonmonkey.pyi index 656ec42d..95595373 100644 --- a/python/pythonmonkey/pythonmonkey.pyi +++ b/python/pythonmonkey/pythonmonkey.pyi @@ -4,67 +4,82 @@ import typing as _typing + class EvalOptions(_typing.TypedDict, total=False): - filename: str - lineno: int - column: int - mutedErrors: bool - noScriptRval: bool - selfHosting: bool - strict: bool - module: bool - fromPythonFrame: bool + filename: str + lineno: int + column: int + mutedErrors: bool + noScriptRval: bool + selfHosting: bool + strict: bool + module: bool + fromPythonFrame: bool # pylint: disable=redefined-builtin + + def eval(code: str, evalOpts: EvalOptions = {}, /) -> _typing.Any: - """ - JavaScript evaluator in Python - """ + """ + JavaScript evaluator in Python + """ + def wait() -> _typing.Awaitable[None]: - """ - Block until all asynchronous jobs (Promise/setTimeout/etc.) finish. - - ```py - await pm.wait() - ``` + """ + Block until all asynchronous jobs (Promise/setTimeout/etc.) finish. + + ```py + await pm.wait() + ``` + + This is the event-loop shield that protects the loop from being prematurely terminated. + """ - This is the event-loop shield that protects the loop from being prematurely terminated. - """ def isCompilableUnit(code: str) -> bool: - """ - Hint if a string might be compilable Javascript without actual evaluation - """ + """ + Hint if a string might be compilable Javascript without actual evaluation + """ + def collect() -> None: - """ - Calls the spidermonkey garbage collector - """ + """ + Calls the spidermonkey garbage collector + """ + + class JSFunctionProxy(): """ JavaScript Function proxy """ + + class JSMethodProxy(JSFunctionProxy, object): + """ + JavaScript Method proxy + This constructs a callable object based on the first argument, bound to the second argument + Useful when you wish to implement a method on a class object with JavaScript + Example: + import pythonmonkey as pm + + jsFunc = pm.eval("(function(value) { this.value = value})") + class Class: + def __init__(self): + self.value = 0 + self.setValue = pm.JSMethodProxy(jsFunc, self) #setValue will be bound to self, so `this` will always be `self` + + myObject = Class() + print(myObject.value) # 0 + myObject.setValue(42) + print(myObject.value) # 42.0 + """ + + def __init__(self) -> None: """ - JavaScript Method proxy - This constructs a callable object based on the first argument, bound to the second argument - Useful when you wish to implement a method on a class object with JavaScript - Example: - import pythonmonkey as pm - - jsFunc = pm.eval("(function(value) { this.value = value})") - class Class: - def __init__(self): - self.value = 0 - self.setValue = pm.JSMethodProxy(jsFunc, self) #setValue will be bound to self, so `this` will always be `self` - - myObject = Class() - print(myObject.value) # 0 - myObject.setValue(42) - print(myObject.value) # 42.0 + PythonMonkey init function """ - def __init__(self) -> None: ... + null = _typing.Annotated[ _typing.NewType("pythonmonkey.null", object), diff --git a/python/pythonmonkey/require.py b/python/pythonmonkey/require.py index 4feba6ba..957dd585 100644 --- a/python/pythonmonkey/require.py +++ b/python/pythonmonkey/require.py @@ -39,7 +39,9 @@ # # @copyright Copyright (c) 2023-2024 Distributive Corp. -import sys, os, io +import sys +import os +import io from typing import Union, Dict, Literal, List import importlib import importlib.util @@ -47,32 +49,35 @@ import inspect import functools -from . import pythonmonkey as pm +from . import pythonmonkey as pm node_modules = os.path.abspath( os.path.join( - importlib.util.find_spec("pminit").submodule_search_locations[0], # type: ignore + importlib.util.find_spec("pminit").submodule_search_locations[0], # type: ignore "..", "pythonmonkey", "node_modules" ) ) -evalOpts = { 'filename': __file__, 'fromPythonFrame': True } # type: pm.EvalOptions +evalOpts = {'filename': __file__, 'fromPythonFrame': True} # type: pm.EvalOptions # Force to use UTF-8 encoding # Windows may use other encodings / code pages that have many characters missing/unrepresentable -if isinstance(sys.stdin, io.TextIOWrapper): sys.stdin.reconfigure(encoding='utf-8') -if isinstance(sys.stdout, io.TextIOWrapper): sys.stdout.reconfigure(encoding='utf-8') -if isinstance(sys.stderr, io.TextIOWrapper): sys.stderr.reconfigure(encoding='utf-8') +if isinstance(sys.stdin, io.TextIOWrapper): + sys.stdin.reconfigure(encoding='utf-8') +if isinstance(sys.stdout, io.TextIOWrapper): + sys.stdout.reconfigure(encoding='utf-8') +if isinstance(sys.stderr, io.TextIOWrapper): + sys.stderr.reconfigure(encoding='utf-8') # Add some python functions to the global python object for code in this file to use. globalThis = pm.eval("globalThis;", evalOpts) -pm.eval("globalThis.python = { pythonMonkey: {}, stdout: {}, stderr: {} }", evalOpts); +pm.eval("globalThis.python = { pythonMonkey: {}, stdout: {}, stderr: {} }", evalOpts) globalThis.pmEval = pm.eval globalThis.python.pythonMonkey.dir = os.path.dirname(__file__) globalThis.python.pythonMonkey.isCompilableUnit = pm.isCompilableUnit globalThis.python.pythonMonkey.nodeModules = node_modules -globalThis.python.print = print +globalThis.python.print = print globalThis.python.stdout.write = lambda s: sys.stdout.write(s) globalThis.python.stderr.write = lambda s: sys.stderr.write(s) globalThis.python.stdout.read = lambda n: sys.stdout.read(n) @@ -80,7 +85,7 @@ globalThis.python.eval = eval globalThis.python.exec = exec globalThis.python.getenv = os.getenv -globalThis.python.paths = sys.path +globalThis.python.paths = sys.path globalThis.python.exit = pm.eval("""'use strict'; (exit) => function pythonExitWrapper(exitCode) { @@ -94,7 +99,7 @@ exitCode = 1n; exit(exitCode); } -""", evalOpts)(sys.exit); +""", evalOpts)(sys.exit) # bootstrap is effectively a scoping object which keeps us from polluting the global JS scope. # The idea is that we hold a reference to the bootstrap object in Python-load, for use by the @@ -202,8 +207,8 @@ if (ret) return ret; - const err = new Error('file not found: ' + filename); - err.code='ENOENT'; + const err = new Error('file not found: ' + filename); + err.code='ENOENT'; throw err; }, }; @@ -214,38 +219,42 @@ return bootstrap; })(globalThis.python)""", evalOpts) + def statSync_inner(filename: str) -> Union[Dict[str, int], bool]: - """ - Inner function for statSync. - - Returns: - Union[Dict[str, int], False]: The mode of the file or False if the file doesn't exist. - """ - from os import stat - filename = os.path.normpath(filename) - if (os.path.exists(filename)): - sb = stat(filename) - return { 'mode': sb.st_mode } - else: - return False + """ + Inner function for statSync. + + Returns: + Union[Dict[str, int], False]: The mode of the file or False if the file doesn't exist. + """ + from os import stat + filename = os.path.normpath(filename) + if (os.path.exists(filename)): + sb = stat(filename) + return {'mode': sb.st_mode} + else: + return False + def readFileSync(filename, charset) -> str: - """ - Utility function for reading files. - Returns: - str: The contents of the file - """ - filename = os.path.normpath(filename) - with open(filename, "r", encoding=charset) as fileHnd: - return fileHnd.read() + """ + Utility function for reading files. + Returns: + str: The contents of the file + """ + filename = os.path.normpath(filename) + with open(filename, "r", encoding=charset) as fileHnd: + return fileHnd.read() + def existsSync(filename: str) -> bool: - filename = os.path.normpath(filename) - return os.path.exists(filename) + filename = os.path.normpath(filename) + return os.path.exists(filename) + bootstrap.modules.fs.statSync_inner = statSync_inner -bootstrap.modules.fs.readFileSync = readFileSync -bootstrap.modules.fs.existsSync = existsSync +bootstrap.modules.fs.readFileSync = readFileSync +bootstrap.modules.fs.existsSync = existsSync # Read ctx-module module from disk and invoke so that this file is the "main module" and ctx-module has # require and exports symbols injected from the bootstrap object above. Current PythonMonkey bugs @@ -255,38 +264,43 @@ def existsSync(filename: str) -> bool: # lineno should be -5 but jsapi 102 uses unsigned line numbers, so we take the newlines out of the # wrapper prologue to make stack traces line up. with open(node_modules + "/ctx-module/ctx-module.js", "r") as ctxModuleSource: - initCtxModule = pm.eval("""'use strict'; + initCtxModule = pm.eval("""'use strict'; (function moduleWrapper_forCtxModule(require, exports) { """ + ctxModuleSource.read() + """ }) -""", { 'filename': node_modules + "/ctx-module/ctx-module.js", 'lineno': 0 }); +""", {'filename': node_modules + "/ctx-module/ctx-module.js", 'lineno': 0}) initCtxModule(bootstrap.require, bootstrap.modules['ctx-module']) -def load(filename: str) -> Dict: - """ - Loads a python module using the importlib machinery sourcefileloader, prefills it with an exports object and returns the module. - If the module is already loaded, returns it. - - Args: - filename (str): The filename of the python module to load. - - Returns: - : The loaded python module - """ - - filename = os.path.normpath(filename) - name = os.path.basename(filename) - if name not in sys.modules: - sourceFileLoader = machinery.SourceFileLoader(name, filename) - spec: machinery.ModuleSpec = importlib.util.spec_from_loader(sourceFileLoader.name, sourceFileLoader) # type: ignore - module = importlib.util.module_from_spec(spec) - sys.modules[name] = module - module.exports = {} # type: ignore - spec.loader.exec_module(module) # type: ignore - else: - module = sys.modules[name] - return module.exports + +def load(filename: str) -> Dict: + """ + Loads a python module using the importlib machinery sourcefileloader, prefills it with an exports object and returns + the module. + If the module is already loaded, returns it. + + Args: + filename (str): The filename of the python module to load. + + Returns: + : The loaded python module + """ + + filename = os.path.normpath(filename) + name = os.path.basename(filename) + if name not in sys.modules: + sourceFileLoader = machinery.SourceFileLoader(name, filename) + spec: machinery.ModuleSpec = importlib.util.spec_from_loader( + sourceFileLoader.name, sourceFileLoader) # type: ignore + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + module.exports = {} # type: ignore + spec.loader.exec_module(module) # type: ignore + else: + module = sys.modules[name] + return module.exports + + globalThis.python.load = load createRequireInner = pm.eval("""'use strict';( @@ -356,41 +370,46 @@ def load(filename: str) -> Dict: # API: pm.createRequire # We cache the return value of createRequire to always use the same require for the same filename -def createRequire(filename, extraPaths: Union[List[str], Literal[False]] = False, isMain = False): - """ - returns a require function that resolves modules relative to the filename argument. - Conceptually the same as node:module.createRequire(). - - example: - from pythonmonkey import createRequire - require = createRequire(__file__) - require('./my-javascript-module') - """ - fullFilename: str = os.path.abspath(filename) - if (extraPaths): - extraPathsStr = ':'.join(extraPaths) - else: - extraPathsStr = '' - return createRequireInner(fullFilename, bootstrap, extraPathsStr, isMain) + + +def createRequire(filename, extraPaths: Union[List[str], Literal[False]] = False, isMain=False): + """ + returns a require function that resolves modules relative to the filename argument. + Conceptually the same as node:module.createRequire(). + + example: + from pythonmonkey import createRequire + require = createRequire(__file__) + require('./my-javascript-module') + """ + fullFilename: str = os.path.abspath(filename) + if (extraPaths): + extraPathsStr = ':'.join(extraPaths) + else: + extraPathsStr = '' + return createRequireInner(fullFilename, bootstrap, extraPathsStr, isMain) + bootstrap.requireFromDisk = createRequireInner(None, bootstrap, '', False) bootstrap.inspect = bootstrap.requireFromDisk('util').inspect # API: pm.runProgramModule + + def runProgramModule(filename, argv, extraPaths=[]): - """ - Run the program module. This loads the code from disk, sets up the execution environment, and then - invokes the program module (aka main module). The program module is different from other modules in that - 1. it cannot return (must throw) - 2. the outermost block scope is the global scope, effectively making its scope a super-global to - other modules - """ - fullFilename = os.path.abspath(filename) - createRequire(fullFilename, extraPaths, True) - globalThis.__filename = fullFilename; - globalThis.__dirname = os.path.dirname(fullFilename); - with open(fullFilename, encoding="utf-8", mode="r") as mainModuleSource: - pm.eval(mainModuleSource.read(), {'filename': fullFilename}) + """ + Run the program module. This loads the code from disk, sets up the execution environment, and then + invokes the program module (aka main module). The program module is different from other modules in that + 1. it cannot return (must throw) + 2. the outermost block scope is the global scope, effectively making its scope a super-global to + other modules + """ + fullFilename = os.path.abspath(filename) + createRequire(fullFilename, extraPaths, True) + globalThis.__filename = fullFilename + globalThis.__dirname = os.path.dirname(fullFilename) + with open(fullFilename, encoding="utf-8", mode="r") as mainModuleSource: + pm.eval(mainModuleSource.read(), {'filename': fullFilename}) # The pythonmonkey require export. Every time it is used, the stack is inspected so that the filename # passed to createRequire is correct. This is necessary so that relative requires work. If the filename @@ -400,11 +419,14 @@ def runProgramModule(filename, argv, extraPaths=[]): # todo: instead of cwd+__main_virtual__, use a full pathname which includes the directory that the # running python program is in. # + + def require(moduleIdentifier: str): - filename = inspect.stack()[1].filename - if not os.path.exists(filename): - filename = os.path.join(os.getcwd(), "__main_virtual__") - return createRequire(filename)(moduleIdentifier) + filename = inspect.stack()[1].filename + if not os.path.exists(filename): + filename = os.path.join(os.getcwd(), "__main_virtual__") + return createRequire(filename)(moduleIdentifier) + # Restrict what symbols are exposed to the pythonmonkey module. __all__ = ["globalThis", "require", "createRequire", "runProgramModule", "bootstrap"] diff --git a/setup.sh b/setup.sh index 1e3b1d63..8d9b8fce 100755 --- a/setup.sh +++ b/setup.sh @@ -2,6 +2,10 @@ set -euo pipefail IFS=$'\n\t' +# set git hooks +ln -s -f ../../githooks/pre-commit .git/hooks/pre-commit +# set blame ignore file +git config blame.ignorerevsfile .git-blame-ignore-revs # Get number of CPU cores CPUS=$(getconf _NPROCESSORS_ONLN 2>/dev/null || getconf NPROCESSORS_ONLN 2>/dev/null || echo 1) @@ -36,8 +40,25 @@ else POETRY_BIN=`echo ~/.local/bin/poetry` # expand tilde fi $POETRY_BIN self add 'poetry-dynamic-versioning[plugin]' +poetry run pip install autopep8 echo "Done installing dependencies" +echo "Downloading uncrustify source code" +wget -c -q https://github.com/uncrustify/uncrustify/archive/refs/tags/uncrustify-0.78.1.tar.gz +mkdir -p uncrustify-source +tar -xzvf uncrustify-0.78.1.tar.gz -C uncrustify-source --strip-components=1 # strip the root folder +echo "Done downloading uncrustify source code" + +echo "Building uncrustify" +cd uncrustify-source +mkdir -p build +cd build +cmake ../ +make -j4 +cp uncrustify ../../uncrustify +cd ../.. +echo "Done building uncrustify" + echo "Downloading spidermonkey source code" wget -c -q https://ftp.mozilla.org/pub/firefox/releases/115.8.0esr/source/firefox-115.8.0esr.source.tar.xz mkdir -p firefox-source @@ -77,4 +98,4 @@ if [[ "$OSTYPE" == "darwin"* ]]; then # macOS # overrides https://hg.mozilla.org/releases/mozilla-esr102/file/89d799cb/js/src/build/Makefile.in#l83 install_name_tool -id @rpath/$(basename ./libmozjs*) ./libmozjs* # making it work for whatever name the libmozjs dylib is called fi -echo "Done installing spidermonkey" +echo "Done installing spidermonkey" \ No newline at end of file diff --git a/src/BufferType.cc b/src/BufferType.cc index da07daa9..a97b9aa0 100644 --- a/src/BufferType.cc +++ b/src/BufferType.cc @@ -210,10 +210,10 @@ const char *BufferType::_toPyBufferFormatCode(JS::Scalar::Type subtype) { JSObject *BufferType::_newTypedArrayWithBuffer(JSContext *cx, JS::Scalar::Type subtype, JS::HandleObject arrayBuffer) { switch (subtype) { #define NEW_TYPED_ARRAY_WITH_BUFFER(ExternalType, NativeType, Name) \ -case JS::Scalar::Name: \ - return JS_New ## Name ## ArrayWithBuffer(cx, arrayBuffer, 0 /* byteOffset */, -1 /* use up the ArrayBuffer */); + case JS::Scalar::Name: \ + return JS_New ## Name ## ArrayWithBuffer(cx, arrayBuffer, 0 /* byteOffset */, -1 /* use up the ArrayBuffer */); - JS_FOR_EACH_TYPED_ARRAY(NEW_TYPED_ARRAY_WITH_BUFFER) + JS_FOR_EACH_TYPED_ARRAY(NEW_TYPED_ARRAY_WITH_BUFFER) #undef NEW_TYPED_ARRAY_WITH_BUFFER default: // invalid PyErr_SetString(PyExc_TypeError, "Invalid Python buffer type."); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69332a3a..d2e2e854 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(pythonmonkey SHARED ) target_include_directories(pythonmonkey PUBLIC ..) +target_compile_definitions(pythonmonkey PRIVATE BUILD_TYPE="${PM_BUILD_TYPE} $") if(WIN32) set_target_properties( diff --git a/src/ExceptionType.cc b/src/ExceptionType.cc index dcdc8918..6c2d8ee7 100644 --- a/src/ExceptionType.cc +++ b/src/ExceptionType.cc @@ -80,7 +80,10 @@ JSObject *ExceptionType::toJsError(JSContext *cx, PyObject *exceptionValue, PyOb assert(exceptionValue != NULL); // Gather JS context + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-zero-length" JS_ReportErrorASCII(cx, ""); // throw JS error and gather all details + #pragma GCC diagnostic pop JS::ExceptionStack exceptionStack(cx); if (!JS::GetPendingExceptionStack(cx, &exceptionStack)) { diff --git a/src/JSFunctionProxy.cc b/src/JSFunctionProxy.cc index 9fc66996..d545f739 100644 --- a/src/JSFunctionProxy.cc +++ b/src/JSFunctionProxy.cc @@ -45,7 +45,11 @@ PyObject *JSFunctionProxyMethodDefinitions::JSFunctionProxy_call(PyObject *self, if (PyErr_Occurred()) { // Check if an exception has already been set in the flow of control return NULL; // Fail-fast } - jsArgsVector.append(jsValue); + if (!jsArgsVector.append(jsValue)) { + // out of memory + setSpiderMonkeyException(cx); + return NULL; + } } JS::HandleValueArray jsArgs(jsArgsVector); diff --git a/src/JSMethodProxy.cc b/src/JSMethodProxy.cc index 4f369b65..78e1189b 100644 --- a/src/JSMethodProxy.cc +++ b/src/JSMethodProxy.cc @@ -56,7 +56,11 @@ PyObject *JSMethodProxyMethodDefinitions::JSMethodProxy_call(PyObject *self, PyO if (PyErr_Occurred()) { // Check if an exception has already been set in the flow of control return NULL; // Fail-fast } - jsArgsVector.append(jsValue); + if (!jsArgsVector.append(jsValue)) { + // out of memory + setSpiderMonkeyException(cx); + return NULL; + } } JS::HandleValueArray jsArgs(jsArgsVector); diff --git a/tests/js/collide.simple b/tests/js/collide.simple index 0308107e..7902a26b 100644 --- a/tests/js/collide.simple +++ b/tests/js/collide.simple @@ -6,15 +6,15 @@ * @date Mar 2024 */ -const { which } = require('./modules/collide') +const { which } = require('./modules/collide'); const whichjs = require('./modules/collide.js').which; const whichpy = require('./modules/collide.py').which; if (which !== 'python') - throw new Error(`python module was not preferred, got ${which} instead`) + throw new Error(`python module was not preferred, got ${which} instead`); if (whichpy !== 'python') - throw new Error(`python module was not explicitly loaded, got ${whichpy} instead`) + throw new Error(`python module was not explicitly loaded, got ${whichpy} instead`); if (whichjs !== 'javascript') - throw new Error(`javascript module was not explicitly loaded, got ${whichjs} instead`) + throw new Error(`javascript module was not explicitly loaded, got ${whichjs} instead`); diff --git a/tests/js/eval-test.simple b/tests/js/eval-test.simple index 152fc554..e511a8a7 100644 --- a/tests/js/eval-test.simple +++ b/tests/js/eval-test.simple @@ -12,7 +12,7 @@ import os globalThis = pm.eval('globalThis') globalThis.result = pm.eval(open(os.path.join(os.getcwd(), "tests", "js", "resources", "eval-test.js"), "rb")) -`) +`); -if (result !== 18436572) +if (globalThis.result !== 18436572) throw new Error('incorrect result from eval-test.js'); diff --git a/tests/js/js2py/array-change-index.simple b/tests/js/js2py/array-change-index.simple index fbd5b0a9..20a04830 100644 --- a/tests/js/js2py/array-change-index.simple +++ b/tests/js/js2py/array-change-index.simple @@ -14,7 +14,7 @@ def setArrayAtIndex(array, index, new): array[1] = new # can't index "array" based on "index"... probably because index is a float. So just pass "1" return array `); -const setArrayAtIndex = python.eval("setArrayAtIndex") +const setArrayAtIndex = python.eval('setArrayAtIndex'); const numbersBack = setArrayAtIndex(numbers, 1, 999); // check that the array data was modified by reference in python diff --git a/tests/js/js2py/error.simple b/tests/js/js2py/error.simple index fde06acf..fd631eba 100644 --- a/tests/js/js2py/error.simple +++ b/tests/js/js2py/error.simple @@ -11,30 +11,35 @@ const throughJS = x => x; let errorFlag = false; -function tester(exceptionpy, exceptionjs) { +function tester(exceptionpy, exceptionjs) +{ if (!exceptionpy.toString() === exceptionjs.toString()) { console.error('Expected\n', exceptionpy.toString(), '\nbut got\n', exceptionjs.toString()); errorFlag = true; - } else { + } + else + { console.log('pass -', exceptionpy.toString()); } } -function inbuiltError() { - const exceptionpy = python.eval(`Exception('I know Python!')`); +function inbuiltError() +{ + const exceptionpy = python.eval('Exception(\'I know Python!\')'); const exceptionjs = throughJS(exceptionpy); tester(exceptionpy, exceptionjs); } -function customError() { +function customError() +{ python.exec( `class IAmAnError(Exception): def __init__(self, message): super().__init__(message) ` ); - const exceptionpy = python.eval(`IAmAnError('I know Python!')`); + const exceptionpy = python.eval('IAmAnError(\'I know Python!\')'); const exceptionjs = throughJS(exceptionpy); tester(exceptionpy, exceptionjs); } @@ -42,7 +47,8 @@ function customError() { inbuiltError(); customError(); -if (errorFlag) { +if (errorFlag) +{ throw new Error('test failed'); } diff --git a/tests/js/js2py/function-curry.simple b/tests/js/js2py/function-curry.simple index bf226cc4..1f37b10d 100644 --- a/tests/js/js2py/function-curry.simple +++ b/tests/js/js2py/function-curry.simple @@ -5,18 +5,24 @@ * @date July 2023 */ -'use strict' -const curry = (fn) => { - return function curried(...args) { - if (args.length >= fn.length) { - return fn.apply(this, args); - } else { - return function(...args2) { - return curried.apply(this, args.concat(args2)); - } +'use strict'; +const curry = (fn) => +{ + return function curried(...args) + { + if (args.length >= fn.length) + { + return fn.apply(null, args); + } + else + { + return function(...args2) + { + return curried.apply(null, args.concat(args2)); + }; } }; -} +}; const takeMiddle = (_x, y, _z) => y; diff --git a/tests/js/js2py/object-mutation.simple b/tests/js/js2py/object-mutation.simple index 2952efdb..623edbd4 100644 --- a/tests/js/js2py/object-mutation.simple +++ b/tests/js/js2py/object-mutation.simple @@ -17,10 +17,10 @@ def change_and_return(obj): python.exec(pcode); -const fun = python.eval("change_and_return"); +const fun = python.eval('change_and_return'); const obj2 = fun(obj); -if (obj.a !== 5 || obj2["a"] !== 5) +if (obj.a !== 5 || obj2['a'] !== 5) { console.error('Object isn\'t sharing memory.'); throw new Error('Test failed'); @@ -28,7 +28,7 @@ if (obj.a !== 5 || obj2["a"] !== 5) obj.a = 1000; -if (obj.a !== 1000 || obj2["a"] !== 1000) +if (obj.a !== 1000 || obj2['a'] !== 1000) { console.error('Object isn\'t sharing memory.'); throw new Error('Test failed'); diff --git a/tests/js/js2py/promise-await-in-python.simple b/tests/js/js2py/promise-await-in-python.simple index 07f021d8..8e9ea6e3 100644 --- a/tests/js/js2py/promise-await-in-python.simple +++ b/tests/js/js2py/promise-await-in-python.simple @@ -6,9 +6,12 @@ */ 'use strict'; -var resolve, reject; +var resolve; var valToResolve = 1; -const examplePromise = new Promise((res, rej) => {resolve = res, reject = rej}); +const examplePromise = new Promise((res, rej) => +{ + resolve = res; +}); const pythonCode = ` import asyncio @@ -19,15 +22,15 @@ async def awaitPromise(promise): `; python.exec(pythonCode); -const pythonFunction = python.eval("awaitPromise") +const pythonFunction = python.eval('awaitPromise'); async function test() { const backValue = await pythonFunction(examplePromise); if (backValue !== 1) - throw new Error(`Received value ${backValue} instead of ${valToResolve} from awaiting a JS promise in python`) + throw new Error(`Received value ${backValue} instead of ${valToResolve} from awaiting a JS promise in python`); } -test() +test(); resolve(valToResolve); diff --git a/tests/js/js2py/promise.simple b/tests/js/js2py/promise.simple index 503b38d9..af26cc85 100644 --- a/tests/js/js2py/promise.simple +++ b/tests/js/js2py/promise.simple @@ -8,16 +8,20 @@ 'use strict'; -var resolve, reject; -const examplePromise = new Promise((res, rej) => {resolve = res, reject = rej}); +var resolve; +const examplePromise = new Promise((res, rej) => +{ + resolve = res; +}); -const pythonCode = `lambda x: x`; +const pythonCode = 'lambda x: x'; const pythonLambda = python.eval(pythonCode); const outPromise = pythonLambda(examplePromise); -outPromise.then(() => { - console.log("able to resolve promise after going through python"); +outPromise.then(() => +{ + console.log('able to resolve promise after going through python'); python.exit(); }); diff --git a/tests/js/modules/collide.js b/tests/js/modules/collide.js index 38eae7f9..832f009a 100644 --- a/tests/js/modules/collide.js +++ b/tests/js/modules/collide.js @@ -1 +1 @@ -exports.which = "javascript" +exports.which = 'javascript'; diff --git a/tests/js/modules/collide.py b/tests/js/modules/collide.py index dfd3ccff..c6e221cd 100644 --- a/tests/js/modules/collide.py +++ b/tests/js/modules/collide.py @@ -1 +1 @@ -exports['which']="python" +exports['which'] = "python" diff --git a/tests/js/modules/print-load.js b/tests/js/modules/print-load.js index d0ea56df..2be4fe02 100644 --- a/tests/js/modules/print-load.js +++ b/tests/js/modules/print-load.js @@ -1,2 +1 @@ -//python.print('LOADED', __file) -console.log('LOADED', __filename) +console.log('LOADED', __filename); diff --git a/tests/js/modules/python-cjs-module.py b/tests/js/modules/python-cjs-module.py index 71c892de..72de3161 100644 --- a/tests/js/modules/python-cjs-module.py +++ b/tests/js/modules/python-cjs-module.py @@ -6,4 +6,5 @@ def helloWorld(): print('hello, world!') + exports['helloWorld'] = helloWorld diff --git a/tests/js/program-module.simple b/tests/js/program-module.simple index 71726067..48c0e910 100644 --- a/tests/js/program-module.simple +++ b/tests/js/program-module.simple @@ -19,14 +19,14 @@ function check(message, testResult) check('require.main is an object', typeof require.main === 'object'); check('require.main is the current module', require.main === module); -check('require is the global require', require === globalThis.require) +check('require is the global require', require === globalThis.require); check('exports is the global exports', exports === globalThis.exports); check('module is the global module', module === globalThis.module); // eslint-disable-next-line no-global-assign module = {}; check('free module symbol is global symbol', module === globalThis.module); -check('arguments.length is ' + arguments.length, ...arguments) +check('arguments.length is ' + arguments.length, ...arguments); if (failures) console.log(`${failures} sub-tests failing`); diff --git a/tests/js/py2js/datetime.simple b/tests/js/py2js/datetime.simple index f9ec5cdc..61a82484 100644 --- a/tests/js/py2js/datetime.simple +++ b/tests/js/py2js/datetime.simple @@ -6,7 +6,7 @@ * @author Elijah Deluzio, elijah@distributive.network * @date July 2023 */ -'use strict' +'use strict'; const throughJS = x => x; var expectedJsTimestamp; @@ -16,8 +16,8 @@ var pyDate; // Test 1: Date from timestamp of 0 (1970 - 01 - 01), timestamp = 0 expectedJsTimestamp = 0; -python.exec(`from datetime import timezone`) -python.exec(`import datetime`); +python.exec('from datetime import timezone'); +python.exec('import datetime'); pyDate = python.eval(`datetime.datetime.fromtimestamp(${expectedJsTimestamp}, tz=timezone.utc)`); jsDate = throughJS(pyDate); @@ -31,8 +31,8 @@ console.log('pass -', jsDate); // Test 2: Date from 21st century (2222 - 02 - 03), timestamp = 7955193600 expectedJsTimestamp = 7955193600; -python.exec(`from datetime import timezone`) -python.exec(`import datetime`); +python.exec('from datetime import timezone'); +python.exec('import datetime'); pyDate = python.eval(`datetime.datetime.fromtimestamp(${expectedJsTimestamp}, tz=timezone.utc)`); jsDate = throughJS(pyDate); diff --git a/tests/js/py2js/error.simple b/tests/js/py2js/error.simple index 90dadfc6..92212709 100644 --- a/tests/js/py2js/error.simple +++ b/tests/js/py2js/error.simple @@ -8,17 +8,20 @@ */ 'use strict'; -class RandomError extends Error { - constructor(message) { +class RandomError extends Error +{ + constructor(message) + { super(message); } } -const exceptionjs = new RandomError("I was created!"); +const exceptionjs = new RandomError('I was created!'); const throughPython = python.eval('(lambda x: x)'); const exceptionpy = throughPython(exceptionjs); -if (!exceptionpy.toString().includes(exceptionjs.toString())) { +if (!exceptionpy.toString().includes(exceptionjs.toString())) +{ console.error('Expected\n', exceptionjs.toString(), '\nbut got\n', exceptionpy.toString()); throw new Error('test failed'); } diff --git a/tests/js/py2js/integer.simple b/tests/js/py2js/integer.simple index ef3043fd..74d2a233 100644 --- a/tests/js/py2js/integer.simple +++ b/tests/js/py2js/integer.simple @@ -15,7 +15,7 @@ if (typeof number !== 'number') } if (number !== 777) { - console.error(`expected ${777} but got ${bigint}`); + console.error(`expected ${777} but got ${number}`); throw new Error('test failed'); } diff --git a/tests/js/set-timeout.simple b/tests/js/set-timeout.simple index 01a6ea8d..322b5ff6 100644 --- a/tests/js/set-timeout.simple +++ b/tests/js/set-timeout.simple @@ -17,7 +17,7 @@ const start = time(); */ function check100ms() { - end = time(); + let end = time(); if (end - start < 0.1) throw new Error('timer fired too soon'); diff --git a/tests/js/test-atob-btoa.simple b/tests/js/test-atob-btoa.simple index 29d82768..75db5622 100644 --- a/tests/js/test-atob-btoa.simple +++ b/tests/js/test-atob-btoa.simple @@ -5,15 +5,17 @@ * @date July 2023 */ -function expect(a) { +function expect(a) +{ return { - toBe(b) { + toBe(b) + { if (a !== b) throw new Error(`'${a}' does not equal to '${b}'`); } - } + }; } -/*! +/* ! * Modified from https://github.com/oven-sh/bun/blob/bun-v0.6.12/test/js/web/util/atob.test.js * Bun * MIT License @@ -22,37 +24,37 @@ function expect(a) { // // atob // -expect(atob("YQ==")).toBe("a"); -expect(atob("YWI=")).toBe("ab"); -expect(atob("YWJj")).toBe("abc"); -expect(atob("YWJjZA==")).toBe("abcd"); -expect(atob("YWJjZGU=")).toBe("abcde"); -expect(atob("YWJjZGVm")).toBe("abcdef"); -expect(atob("zzzz")).toBe("Ï<ó"); -expect(atob("")).toBe(""); -expect(atob("null")).toBe("žée"); -expect(atob("6ek=")).toBe("éé"); +expect(atob('YQ==')).toBe('a'); +expect(atob('YWI=')).toBe('ab'); +expect(atob('YWJj')).toBe('abc'); +expect(atob('YWJjZA==')).toBe('abcd'); +expect(atob('YWJjZGU=')).toBe('abcde'); +expect(atob('YWJjZGVm')).toBe('abcdef'); +expect(atob('zzzz')).toBe('Ï<ó'); +expect(atob('')).toBe(''); +expect(atob('null')).toBe('žée'); +expect(atob('6ek=')).toBe('éé'); // expect(atob("6ek")).toBe("éé"); // FIXME (Tom Tang): binascii.Error: Incorrect padding -expect(atob("gIE=")).toBe("€"); +expect(atob('gIE=')).toBe('€'); // expect(atob("zz")).toBe("Ï"); // FIXME (Tom Tang): same here // expect(atob("zzz")).toBe("Ï<"); -expect(atob("zzz=")).toBe("Ï<"); -expect(atob(" YQ==")).toBe("a"); -expect(atob("YQ==\u000a")).toBe("a"); +expect(atob('zzz=')).toBe('Ï<'); +expect(atob(' YQ==')).toBe('a'); +expect(atob('YQ==\u000a')).toBe('a'); // // btoa // -expect(btoa("a")).toBe("YQ=="); -expect(btoa("ab")).toBe("YWI="); -expect(btoa("abc")).toBe("YWJj"); -expect(btoa("abcd")).toBe("YWJjZA=="); -expect(btoa("abcde")).toBe("YWJjZGU="); -expect(btoa("abcdef")).toBe("YWJjZGVm"); -expect(typeof btoa).toBe("function"); -expect(btoa("")).toBe(""); -expect(btoa("null")).toBe("bnVsbA=="); -expect(btoa("undefined")).toBe("dW5kZWZpbmVk"); -expect(btoa("[object Window]")).toBe("W29iamVjdCBXaW5kb3dd"); -expect(btoa("éé")).toBe("6ek="); -expect(btoa("\u0080\u0081")).toBe("gIE="); +expect(btoa('a')).toBe('YQ=='); +expect(btoa('ab')).toBe('YWI='); +expect(btoa('abc')).toBe('YWJj'); +expect(btoa('abcd')).toBe('YWJjZA=='); +expect(btoa('abcde')).toBe('YWJjZGU='); +expect(btoa('abcdef')).toBe('YWJjZGVm'); +expect(typeof btoa).toBe('function'); +expect(btoa('')).toBe(''); +expect(btoa('null')).toBe('bnVsbA=='); +expect(btoa('undefined')).toBe('dW5kZWZpbmVk'); +expect(btoa('[object Window]')).toBe('W29iamVjdCBXaW5kb3dd'); +expect(btoa('éé')).toBe('6ek='); +expect(btoa('\u0080\u0081')).toBe('gIE='); diff --git a/tests/js/throw-filename.js b/tests/js/throw-filename.js index ecaa4d53..d29cf743 100644 --- a/tests/js/throw-filename.js +++ b/tests/js/throw-filename.js @@ -6,9 +6,9 @@ */ try { - throw new Error("lp0 on fire"); + throw new Error('lp0 on fire'); } catch(error) { - console.log(error.stack) + console.log(error.stack); } diff --git a/tests/js/typeofs.simple b/tests/js/typeofs.simple index e9e56859..80a60467 100755 --- a/tests/js/typeofs.simple +++ b/tests/js/typeofs.simple @@ -17,7 +17,7 @@ function check(jsval, expected) switch (typeof expected) { case 'function': - disp = expected.name || '(anonymous function)' + disp = expected.name || '(anonymous function)'; break; case 'object': disp = JSON.stringify(expected); @@ -42,16 +42,16 @@ function check(jsval, expected) } } -check(throughBoth(123) , 'number') -check(throughBoth(123n), 'bigint') -check(throughBoth('sn'), 'string') -check(throughBoth({}), 'object') -check(throughBoth(true), 'boolean') -check(throughBoth(false), 'boolean') -check(throughBoth(undefined), 'undefined') -check(throughBoth(null), 'object') -check(throughBoth([1,1]), 'object') -check(throughBoth(()=>1), 'function') -check(throughBoth([2,2]), Array) -check(throughBoth(new Array(1)), Array) +check(throughBoth(123) , 'number'); +check(throughBoth(123n), 'bigint'); +check(throughBoth('sn'), 'string'); +check(throughBoth({}), 'object'); +check(throughBoth(true), 'boolean'); +check(throughBoth(false), 'boolean'); +check(throughBoth(undefined), 'undefined'); +check(throughBoth(null), 'object'); +check(throughBoth([1,1]), 'object'); +check(throughBoth(()=>1), 'function'); +check(throughBoth([2,2]), Array); +check(throughBoth(new Array(1)), Array); /** see also typeofs-segfaults.simple.failing */ diff --git a/tests/js/url-search-params.simple b/tests/js/url-search-params.simple index 76569d0c..c51ded5f 100644 --- a/tests/js/url-search-params.simple +++ b/tests/js/url-search-params.simple @@ -18,7 +18,8 @@ const NODE = false; const { getPrototypeOf, getOwnPropertyDescriptor } = Object; -QUnit.test('URLSearchParams', assert => { +QUnit.test('URLSearchParams', assert => +{ assert.isFunction(URLSearchParams); assert.arity(URLSearchParams, 0); assert.name(URLSearchParams, 'URLSearchParams'); @@ -124,21 +125,26 @@ QUnit.test('URLSearchParams', assert => { // !!! { input: { 'a\0b': '42', 'c\uD83D': '23', dሴ: 'foo' }, output: [['a\0b', '42'], ['c\uFFFD', '23'], ['d\u1234', 'foo']], name: 'object with NULL, non-ASCII, and surrogate keys' }, ]; - for (const { input, output, name } of testData) { + for (const { input, output, name } of testData) + { params = new URLSearchParams(input); let i = 0; - params.forEach((value, key) => { + params.forEach((value, key) => + { const [reqKey, reqValue] = output[i++]; assert.same(key, reqKey, `construct with ${ name }`); assert.same(value, reqValue, `construct with ${ name }`); }); } - assert.throws(() => { + assert.throws(() => + { + // eslint-disable-next-line new-cap URLSearchParams(''); }, 'TypeError: Incorrect invocation'); // throws w/o `new` - assert.throws(() => { + assert.throws(() => + { new URLSearchParams([[1, 2, 3]]); }, 'TypeError: Expected sequence with length 2'); @@ -146,7 +152,8 @@ QUnit.test('URLSearchParams', assert => { // new URLSearchParams([createIterable([createIterable([1, 2, 3])])]); // }, 'sequence elements must be pairs #2'); - assert.throws(() => { + assert.throws(() => + { new URLSearchParams([[1]]); }, 'TypeError: Expected sequence with length 2'); @@ -155,7 +162,8 @@ QUnit.test('URLSearchParams', assert => { // }, 'sequence elements must be pairs #4'); }); -QUnit.test('URLSearchParams#append', assert => { +QUnit.test('URLSearchParams#append', assert => +{ const { append } = URLSearchParams.prototype; assert.isFunction(append); assert.arity(append, 2); @@ -203,12 +211,14 @@ QUnit.test('URLSearchParams#append', assert => { params.append('first', 10); assert.same(params.get('first'), '1', 'search params object has name "first" with value "1"'); - assert.throws(() => { + assert.throws(() => + { return new URLSearchParams('').append(); }, 'TypeError: Not enough arguments'); // throws w/o arguments }); -QUnit.test('URLSearchParams#delete', assert => { +QUnit.test('URLSearchParams#delete', assert => +{ const $delete = URLSearchParams.prototype.delete; assert.isFunction($delete); assert.arity($delete, 1); @@ -258,7 +268,8 @@ QUnit.test('URLSearchParams#delete', assert => { params.delete('a', undefined); assert.same(String(params), 'b=4'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { let url = new URL('http://example.com/?param1¶m2'); url.searchParams.delete('param1'); url.searchParams.delete('param2'); @@ -271,12 +282,14 @@ QUnit.test('URLSearchParams#delete', assert => { assert.same(url.search, '', 'url.search does not have ?'); } - assert.throws(() => { + assert.throws(() => + { return new URLSearchParams('').delete(); }, 'TypeError: Not enough arguments'); }); -QUnit.test('URLSearchParams#get', assert => { +QUnit.test('URLSearchParams#get', assert => +{ const { get } = URLSearchParams.prototype; assert.isFunction(get); assert.arity(get, 1); @@ -338,12 +351,14 @@ QUnit.test('URLSearchParams#get', assert => { assert.same(new URLSearchParams('=').get(''), '', 'parse ='); - assert.throws(() => { + assert.throws(() => + { return new URLSearchParams('').get(); }, 'TypeError: Not enough arguments'); }); -QUnit.test('URLSearchParams#getAll', assert => { +QUnit.test('URLSearchParams#getAll', assert => +{ const { getAll } = URLSearchParams.prototype; assert.isFunction(getAll); assert.arity(getAll, 1); @@ -370,12 +385,14 @@ QUnit.test('URLSearchParams#getAll', assert => { params.set('a', 'one'); assert.arrayEqual(params.getAll('a'), ['one'], 'search params object has expected name "a" values'); - assert.throws(() => { + assert.throws(() => + { return new URLSearchParams('').getAll(); }, 'TypeError: Not enough arguments'); }); -QUnit.test('URLSearchParams#has', assert => { +QUnit.test('URLSearchParams#has', assert => +{ const { has } = URLSearchParams.prototype; assert.isFunction(has); assert.arity(has, 1); @@ -416,12 +433,14 @@ QUnit.test('URLSearchParams#has', assert => { assert.true(params.has('b', undefined)); assert.false(params.has('c', undefined)); - assert.throws(() => { + assert.throws(() => + { return new URLSearchParams('').has(); }, 'TypeError: Not enough arguments'); }); -QUnit.test('URLSearchParams#set', assert => { +QUnit.test('URLSearchParams#set', assert => +{ const { set } = URLSearchParams.prototype; assert.isFunction(set); assert.arity(set, 2); @@ -451,12 +470,14 @@ QUnit.test('URLSearchParams#set', assert => { assert.same(params.get('a'), '4', 'search params object has name "a" with value "4"'); assert.same(String(params), 'a=4&first=4'); - assert.throws(() => { + assert.throws(() => + { return new URLSearchParams('').set(); }, 'TypeError: Not enough arguments'); }); -QUnit.test('URLSearchParams#sort', assert => { +QUnit.test('URLSearchParams#sort', assert => +{ const { sort } = URLSearchParams.prototype; assert.isFunction(sort); assert.arity(sort, 0); @@ -523,11 +544,13 @@ QUnit.test('URLSearchParams#sort', assert => { }, ]; - for (const { input, output } of testData) { + for (const { input, output } of testData) + { let i = 0; params = new URLSearchParams(input); params.sort(); - params.forEach((value, key) => { + params.forEach((value, key) => + { const [reqKey, reqValue] = output[i++]; assert.same(key, reqKey); assert.same(value, reqValue); @@ -537,14 +560,16 @@ QUnit.test('URLSearchParams#sort', assert => { const url = new URL(`?${ input }`, 'https://example/'); params = url.searchParams; params.sort(); - params.forEach((value, key) => { + params.forEach((value, key) => + { const [reqKey, reqValue] = output[i++]; assert.same(key, reqKey); assert.same(value, reqValue); }); } - if (DESCRIPTORS) { + if (DESCRIPTORS) + { const url = new URL('http://example.com/?'); url.searchParams.sort(); assert.same(url.href, 'http://example.com/', 'Sorting non-existent params removes ? from URL'); @@ -552,7 +577,8 @@ QUnit.test('URLSearchParams#sort', assert => { } }); -QUnit.test('URLSearchParams#toString', assert => { +QUnit.test('URLSearchParams#toString', assert => +{ const { toString } = URLSearchParams.prototype; assert.isFunction(toString); assert.arity(toString, 0); @@ -650,7 +676,8 @@ QUnit.test('URLSearchParams#toString', assert => { assert.same(String(params), 'a=&a=b'); }); -QUnit.test('URLSearchParams#forEach', assert => { +QUnit.test('URLSearchParams#forEach', assert => +{ const { forEach } = URLSearchParams.prototype; assert.isFunction(forEach); assert.arity(forEach, 1); @@ -661,7 +688,8 @@ QUnit.test('URLSearchParams#forEach', assert => { const expectedValues = { a: '1', b: '2', c: '3' }; let params = new URLSearchParams('a=1&b=2&c=3'); let result = ''; - params.forEach((value, key, that) => { + params.forEach((value, key, that) => + { assert.same(params.get(key), expectedValues[key]); assert.same(value, expectedValues[key]); assert.same(that, params); @@ -669,16 +697,19 @@ QUnit.test('URLSearchParams#forEach', assert => { }); assert.same(result, 'abc'); - new URL('http://a.b/c').searchParams.forEach(() => { + new URL('http://a.b/c').searchParams.forEach(() => + { assert.avoid(); }); // fails in Chrome 66- - if (DESCRIPTORS) { + if (DESCRIPTORS) + { const url = new URL('http://a.b/c?a=1&b=2&c=3&d=4'); params = url.searchParams; result = ''; - params.forEach((val, key) => { + params.forEach((val, key) => + { url.search = 'x=1&y=2&z=3'; result += key + val; }); @@ -688,14 +719,16 @@ QUnit.test('URLSearchParams#forEach', assert => { // fails in Chrome 66- params = new URLSearchParams('a=1&b=2&c=3'); result = ''; - params.forEach((value, key) => { + params.forEach((value, key) => + { params.delete('b'); result += key + value; }); assert.same(result, 'a1c3'); }); -QUnit.test('URLSearchParams#entries', assert => { +QUnit.test('URLSearchParams#entries', assert => +{ const { entries } = URLSearchParams.prototype; assert.isFunction(entries); assert.arity(entries, 0); @@ -708,7 +741,8 @@ QUnit.test('URLSearchParams#entries', assert => { let iterator = params.entries(); let result = ''; let entry; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { const [key, value] = entry.value; assert.same(params.get(key), expectedValues[key]); assert.same(value, expectedValues[key]); @@ -719,11 +753,13 @@ QUnit.test('URLSearchParams#entries', assert => { assert.true(new URL('http://a.b/c').searchParams.entries().next().done, 'should be finished'); // fails in Chrome 66- - if (DESCRIPTORS) { + if (DESCRIPTORS) + { const url = new URL('http://a.b/c?a=1&b=2&c=3&d=4'); iterator = url.searchParams.entries(); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { const [key, value] = entry.value; url.search = 'x=1&y=2&z=3'; result += key + value; @@ -735,7 +771,8 @@ QUnit.test('URLSearchParams#entries', assert => { params = new URLSearchParams('a=1&b=2&c=3'); iterator = params.entries(); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { params.delete('b'); const [key, value] = entry.value; result += key + value; @@ -745,7 +782,8 @@ QUnit.test('URLSearchParams#entries', assert => { if (DESCRIPTORS) assert.true(getOwnPropertyDescriptor(getPrototypeOf(new URLSearchParams().entries()), 'next').enumerable, 'enumerable .next'); }); -QUnit.test('URLSearchParams#keys', assert => { +QUnit.test('URLSearchParams#keys', assert => +{ const { keys } = URLSearchParams.prototype; assert.isFunction(keys); assert.arity(keys, 0); @@ -756,7 +794,8 @@ QUnit.test('URLSearchParams#keys', assert => { let iterator = new URLSearchParams('a=1&b=2&c=3').keys(); let result = ''; let entry; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { result += entry.value; } assert.same(result, 'abc'); @@ -764,11 +803,13 @@ QUnit.test('URLSearchParams#keys', assert => { assert.true(new URL('http://a.b/c').searchParams.keys().next().done, 'should be finished'); // fails in Chrome 66- - if (DESCRIPTORS) { + if (DESCRIPTORS) + { const url = new URL('http://a.b/c?a=1&b=2&c=3&d=4'); iterator = url.searchParams.keys(); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { const key = entry.value; url.search = 'x=1&y=2&z=3'; result += key; @@ -780,7 +821,8 @@ QUnit.test('URLSearchParams#keys', assert => { const params = new URLSearchParams('a=1&b=2&c=3'); iterator = params.keys(); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { params.delete('b'); const key = entry.value; result += key; @@ -790,7 +832,8 @@ QUnit.test('URLSearchParams#keys', assert => { if (DESCRIPTORS) assert.true(getOwnPropertyDescriptor(getPrototypeOf(new URLSearchParams().keys()), 'next').enumerable, 'enumerable .next'); }); -QUnit.test('URLSearchParams#values', assert => { +QUnit.test('URLSearchParams#values', assert => +{ const { values } = URLSearchParams.prototype; assert.isFunction(values); assert.arity(values, 0); @@ -801,7 +844,8 @@ QUnit.test('URLSearchParams#values', assert => { let iterator = new URLSearchParams('a=1&b=2&c=3').values(); let result = ''; let entry; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { result += entry.value; } assert.same(result, '123'); @@ -809,11 +853,13 @@ QUnit.test('URLSearchParams#values', assert => { assert.true(new URL('http://a.b/c').searchParams.values().next().done, 'should be finished'); // fails in Chrome 66- - if (DESCRIPTORS) { + if (DESCRIPTORS) + { const url = new URL('http://a.b/c?a=a&b=b&c=c&d=d'); iterator = url.searchParams.keys(); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { const { value } = entry; url.search = 'x=x&y=y&z=z'; result += value; @@ -825,7 +871,8 @@ QUnit.test('URLSearchParams#values', assert => { const params = new URLSearchParams('a=1&b=2&c=3'); iterator = params.values(); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { params.delete('b'); const key = entry.value; result += key; @@ -835,7 +882,8 @@ QUnit.test('URLSearchParams#values', assert => { if (DESCRIPTORS) assert.true(getOwnPropertyDescriptor(getPrototypeOf(new URLSearchParams().values()), 'next').enumerable, 'enumerable .next'); }); -QUnit.test('URLSearchParams#@@iterator', assert => { +QUnit.test('URLSearchParams#@@iterator', assert => +{ const entries = URLSearchParams.prototype[Symbol.iterator]; assert.isFunction(entries); assert.arity(entries, 0); @@ -849,7 +897,8 @@ QUnit.test('URLSearchParams#@@iterator', assert => { let iterator = params[Symbol.iterator](); let result = ''; let entry; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { const [key, value] = entry.value; assert.same(params.get(key), expectedValues[key]); assert.same(value, expectedValues[key]); @@ -860,11 +909,13 @@ QUnit.test('URLSearchParams#@@iterator', assert => { assert.true(new URL('http://a.b/c').searchParams[Symbol.iterator]().next().done, 'should be finished'); // fails in Chrome 66- - if (DESCRIPTORS) { + if (DESCRIPTORS) + { const url = new URL('http://a.b/c?a=1&b=2&c=3&d=4'); iterator = url.searchParams[Symbol.iterator](); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { const [key, value] = entry.value; url.search = 'x=1&y=2&z=3'; result += key + value; @@ -876,7 +927,8 @@ QUnit.test('URLSearchParams#@@iterator', assert => { params = new URLSearchParams('a=1&b=2&c=3'); iterator = params[Symbol.iterator](); result = ''; - while (!(entry = iterator.next()).done) { + while (!(entry = iterator.next()).done) + { params.delete('b'); const [key, value] = entry.value; result += key + value; @@ -886,12 +938,14 @@ QUnit.test('URLSearchParams#@@iterator', assert => { if (DESCRIPTORS) assert.true(getOwnPropertyDescriptor(getPrototypeOf(new URLSearchParams()[Symbol.iterator]()), 'next').enumerable, 'enumerable .next'); }); -QUnit.test('URLSearchParams#size', assert => { +QUnit.test('URLSearchParams#size', assert => +{ const params = new URLSearchParams('a=1&b=2&b=3'); assert.true('size' in params); assert.same(params.size, 3); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.true('size' in URLSearchParams.prototype); const { enumerable, configurable, get } = getOwnPropertyDescriptor(URLSearchParams.prototype, 'size'); @@ -905,15 +959,19 @@ QUnit.test('URLSearchParams#size', assert => { } }); -QUnit.test('URLSearchParams#@@toStringTag', assert => { +QUnit.test('URLSearchParams#@@toStringTag', assert => +{ const params = new URLSearchParams('a=b'); assert.same(({}).toString.call(params), '[object URLSearchParams]'); }); -if (typeof Request == 'function') { - QUnit.test('URLSearchParams with Request', assert => { +if (typeof Request === 'function') +{ + QUnit.test('URLSearchParams with Request', assert => + { const async = assert.async(); - new Request('http://zloirock.ru', { body: new URLSearchParams({ foo: 'baz' }), method: 'POST' }).text().then(text => { + new Request('http://zloirock.ru', { body: new URLSearchParams({ foo: 'baz' }), method: 'POST' }).text().then(text => + { assert.same(text, 'foo=baz'); async(); }); diff --git a/tests/js/url.simple b/tests/js/url.simple index 5aada95b..68a3083d 100644 --- a/tests/js/url.simple +++ b/tests/js/url.simple @@ -18,7 +18,8 @@ const NODE = false; const { hasOwnProperty } = Object.prototype; -QUnit.test('URL constructor', assert => { +QUnit.test('URL constructor', assert => +{ assert.isFunction(URL); if (!NODE) assert.arity(URL, 1); assert.name(URL, 'URL'); @@ -67,10 +68,12 @@ QUnit.test('URL constructor', assert => { assert.throws(() => new URL('1http://zloirock.ru'), 'TypeError: Invalid scheme'); }); -QUnit.test('URL#href', assert => { +QUnit.test('URL#href', assert => +{ let url = new URL('http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'href')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'href'); assert.true(descriptor.enumerable); @@ -81,7 +84,8 @@ QUnit.test('URL#href', assert => { assert.same(url.href, 'http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url.searchParams.append('foo', 'bar'); assert.same(url.href, 'http://zloirock.ru/?foo=bar'); @@ -143,10 +147,12 @@ QUnit.test('URL#href', assert => { } }); -QUnit.test('URL#origin', assert => { +QUnit.test('URL#origin', assert => +{ const url = new URL('http://es6.zloirock.ru/tests.html'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'origin')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'origin'); assert.true(descriptor.enumerable); @@ -159,10 +165,12 @@ QUnit.test('URL#origin', assert => { assert.same(new URL('https://測試/tests').origin, 'https://xn--g6w251d'); }); -QUnit.test('URL#protocol', assert => { +QUnit.test('URL#protocol', assert => +{ let url = new URL('http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'protocol')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'protocol'); assert.true(descriptor.enumerable); @@ -173,7 +181,8 @@ QUnit.test('URL#protocol', assert => { assert.same(url.protocol, 'http:'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/'); url.protocol = 'https'; assert.same(url.protocol, 'https:'); @@ -194,10 +203,12 @@ QUnit.test('URL#protocol', assert => { } }); -QUnit.test('URL#username', assert => { +QUnit.test('URL#username', assert => +{ let url = new URL('http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'username')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'username'); assert.true(descriptor.enumerable); @@ -211,7 +222,8 @@ QUnit.test('URL#username', assert => { url = new URL('http://username@zloirock.ru/'); assert.same(url.username, 'username'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/'); url.username = 'username'; assert.same(url.username, 'username'); @@ -219,10 +231,12 @@ QUnit.test('URL#username', assert => { } }); -QUnit.test('URL#password', assert => { +QUnit.test('URL#password', assert => +{ let url = new URL('http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'password')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'password'); assert.true(descriptor.enumerable); @@ -239,7 +253,8 @@ QUnit.test('URL#password', assert => { // url = new URL('http://:password@zloirock.ru/'); // TypeError in FF // assert.same(url.password, 'password'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/'); url.username = 'username'; url.password = 'password'; @@ -253,10 +268,12 @@ QUnit.test('URL#password', assert => { } }); -QUnit.test('URL#host', assert => { +QUnit.test('URL#host', assert => +{ let url = new URL('http://zloirock.ru:81/path'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'host')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'host'); assert.true(descriptor.enumerable); @@ -267,7 +284,8 @@ QUnit.test('URL#host', assert => { assert.same(url.host, 'zloirock.ru:81'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru:81/path'); url.host = 'example.com:82'; assert.same(url.host, 'example.com:82'); @@ -332,10 +350,12 @@ QUnit.test('URL#host', assert => { } }); -QUnit.test('URL#hostname', assert => { +QUnit.test('URL#hostname', assert => +{ let url = new URL('http://zloirock.ru:81/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'hostname')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'hostname'); assert.true(descriptor.enumerable); @@ -346,7 +366,8 @@ QUnit.test('URL#hostname', assert => { assert.same(url.hostname, 'zloirock.ru'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru:81/'); url.hostname = 'example.com'; assert.same(url.hostname, 'example.com'); @@ -404,10 +425,12 @@ QUnit.test('URL#hostname', assert => { } }); -QUnit.test('URL#port', assert => { +QUnit.test('URL#port', assert => +{ let url = new URL('http://zloirock.ru:1337/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'port')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'port'); assert.true(descriptor.enumerable); @@ -418,7 +441,8 @@ QUnit.test('URL#port', assert => { assert.same(url.port, '1337'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/'); url.port = 80; assert.same(url.port, ''); @@ -441,10 +465,12 @@ QUnit.test('URL#port', assert => { } }); -QUnit.test('URL#pathname', assert => { +QUnit.test('URL#pathname', assert => +{ let url = new URL('http://zloirock.ru/foo/bar'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'pathname')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'pathname'); assert.true(descriptor.enumerable); @@ -455,7 +481,8 @@ QUnit.test('URL#pathname', assert => { assert.same(url.pathname, '/foo/bar'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/'); url.pathname = 'bar/baz'; assert.same(url.pathname, '/bar/baz'); @@ -463,10 +490,12 @@ QUnit.test('URL#pathname', assert => { } }); -QUnit.test('URL#search', assert => { +QUnit.test('URL#search', assert => +{ let url = new URL('http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'search')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'search'); assert.true(descriptor.enumerable); @@ -480,7 +509,8 @@ QUnit.test('URL#search', assert => { url = new URL('http://zloirock.ru/?foo=bar'); assert.same(url.search, '?foo=bar'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/?'); assert.same(url.search, ''); assert.same(String(url), 'http://zloirock.ru/?'); @@ -496,10 +526,12 @@ QUnit.test('URL#search', assert => { } }); -QUnit.test('URL#searchParams', assert => { +QUnit.test('URL#searchParams', assert => +{ let url = new URL('http://zloirock.ru/?foo=bar&bar=baz'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'searchParams')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'searchParams'); assert.true(descriptor.enumerable); @@ -511,7 +543,8 @@ QUnit.test('URL#searchParams', assert => { assert.same(url.searchParams.get('foo'), 'bar'); assert.same(url.searchParams.get('bar'), 'baz'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/'); url.searchParams.append('foo', 'bar'); assert.same(String(url), 'http://zloirock.ru/?foo=bar'); @@ -526,10 +559,12 @@ QUnit.test('URL#searchParams', assert => { } }); -QUnit.test('URL#hash', assert => { +QUnit.test('URL#hash', assert => +{ let url = new URL('http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { assert.false(hasOwnProperty.call(url, 'hash')); const descriptor = Object.getOwnPropertyDescriptor(URL.prototype, 'hash'); assert.true(descriptor.enumerable); @@ -547,7 +582,8 @@ QUnit.test('URL#hash', assert => { assert.same(url.hash, ''); assert.same(String(url), 'http://zloirock.ru/#'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url = new URL('http://zloirock.ru/#'); url.hash = 'foo'; assert.same(url.hash, '#foo'); @@ -575,7 +611,8 @@ QUnit.test('URL#hash', assert => { } }); -QUnit.test('URL#toJSON', assert => { +QUnit.test('URL#toJSON', assert => +{ const { toJSON } = URL.prototype; assert.isFunction(toJSON); assert.arity(toJSON, 0); @@ -586,13 +623,15 @@ QUnit.test('URL#toJSON', assert => { const url = new URL('http://zloirock.ru/'); assert.same(url.toJSON(), 'http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url.searchParams.append('foo', 'bar'); assert.same(url.toJSON(), 'http://zloirock.ru/?foo=bar'); } }); -QUnit.test('URL#toString', assert => { +QUnit.test('URL#toString', assert => +{ const { toString } = URL.prototype; assert.isFunction(toString); assert.arity(toString, 0); @@ -603,18 +642,21 @@ QUnit.test('URL#toString', assert => { const url = new URL('http://zloirock.ru/'); assert.same(url.toString(), 'http://zloirock.ru/'); - if (DESCRIPTORS) { + if (DESCRIPTORS) + { url.searchParams.append('foo', 'bar'); assert.same(url.toString(), 'http://zloirock.ru/?foo=bar'); } }); -QUnit.test('URL#@@toStringTag', assert => { +QUnit.test('URL#@@toStringTag', assert => +{ const url = new URL('http://zloirock.ru/'); assert.same(({}).toString.call(url), '[object URL]'); }); -QUnit.test('URL.sham', assert => { +QUnit.test('URL.sham', assert => +{ assert.same(URL.sham, DESCRIPTORS ? undefined : true); }); @@ -624,7 +666,8 @@ QUnit.test('URL.sham', assert => { * MIT License */ -QUnit.test('URL.canParse', assert => { +QUnit.test('URL.canParse', assert => +{ const { canParse } = URL; assert.isFunction(canParse); @@ -646,6 +689,12 @@ QUnit.test('URL.canParse', assert => { assert.true(canParse('x', 'https://login:password@example.com:8080/?a=1&b=2&a=3&c=4#fragment'), 'x, https://login:password@example.com:8080/?a=1&b=2&a=3&c=4#fragment'); assert.throws(() => canParse(), 'TypeError: Not enough arguments'); - assert.throws(() => canParse({ toString() { throw Error('conversion thrower #1'); } }), 'conversion thrower #1'); - assert.throws(() => canParse('q:w', { toString() { throw Error('conversion thrower #2'); } }), 'conversion thrower #2'); + assert.throws(() => canParse({ toString() + { + throw Error('conversion thrower #1'); + } }), 'conversion thrower #1'); + assert.throws(() => canParse('q:w', { toString() + { + throw Error('conversion thrower #2'); + } }), 'conversion thrower #2'); }); diff --git a/tests/js/xhr.simple b/tests/js/xhr.simple index f4c27637..2afddb55 100644 --- a/tests/js/xhr.simple +++ b/tests/js/xhr.simple @@ -56,71 +56,87 @@ const expectedBody = ` `; -new Promise(function (resolve, reject) { +new Promise(function (resolve, reject) +{ let xhr = new XMLHttpRequest(); xhr.open('GET', 'http://www.example.org/'); - xhr.onload = function () { - if (this.status >= 200 && this.status < 300) { + xhr.onload = function () + { + if (this.status >= 200 && this.status < 300) + { resolve(this.response); } - else { - reject({ - status: this.status, - statusText: this.statusText - }); + else + { + reject(new Error({ + status: this.status, + statusText: this.statusText + })); } }; - xhr.onerror = function () { - reject({ + xhr.onerror = function () + { + reject(new Error({ status: this.status, statusText: this.statusText - }); + })); }; xhr.send(); -}).then((value) => { - if (value != expectedBody) { +}).then((value) => +{ + if (value !== expectedBody) + { console.error('expected ', expectedBody, ' but got ', value); throw new Error('Test failed'); } console.log('Test passed'); -}).catch((error) => { - throw error; -}) +}).catch((error) => +{ + throw error; +}); -new Promise(function (resolve, reject) { +new Promise(function (resolve, reject) +{ let xhr = new XMLHttpRequest(); xhr.open('POST', 'http://httpbin.org/post'); - xhr.onload = function () { - if (this.status >= 200 && this.status < 300) { + xhr.onload = function () + { + if (this.status >= 200 && this.status < 300) + { resolve(this.response); } - else { - reject({ - status: this.status, - statusText: this.statusText - }); + else + { + reject(new Error({ + status: this.status, + statusText: this.statusText + })); } }; - xhr.onerror = function () { - reject({ + xhr.onerror = function () + { + reject(new Error({ status: this.status, statusText: this.statusText - }); + })); }; xhr.send(); -}).then((value) => { - value = JSON.parse(value) - if (!value["headers"]["User-Agent"].startsWith("Python/")) { - console.error("expected Python/* User-Agent, but got ", value.headers["User-Agent"]) +}).then((value) => +{ + value = JSON.parse(value); + if (!value['headers']['User-Agent'].startsWith('Python/')) + { + console.error('expected Python/* User-Agent, but got ', value.headers['User-Agent']); } console.log('Test passed'); -}).catch((error) => { - throw error; -}) \ No newline at end of file +}).catch((error) => +{ + throw error; +}); \ No newline at end of file diff --git a/tests/python/conftest.py b/tests/python/conftest.py index 56552c0a..4fceb789 100644 --- a/tests/python/conftest.py +++ b/tests/python/conftest.py @@ -3,11 +3,13 @@ import gc # This is run at the end of each test function + + @pytest.fixture(scope="function", autouse=True) def teardown_function(): - """ - Forcing garbage collection (twice) whenever a test function finishes, - to locate GC-related errors - """ - gc.collect(), pm.collect() - gc.collect(), pm.collect() + """ + Forcing garbage collection (twice) whenever a test function finishes, + to locate GC-related errors + """ + gc.collect(), pm.collect() + gc.collect(), pm.collect() diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index b6240ed0..2f340ea5 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1,837 +1,984 @@ import pythonmonkey as pm from datetime import datetime + def test_assign(): - items = [1,2,3] - pm.eval("(arr) => {arr[0] = 42}")(items) - assert items[0] == 42 + items = [1, 2, 3] + pm.eval("(arr) => {arr[0] = 42}")(items) + assert items[0] == 42 + def test_get(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr[1]}")(result, items) - assert result[0] == 2 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr[1]}")(result, items) + assert result[0] == 2 + def test_get_length(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.length}")(result, items) - assert result[0] == 3 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.length}")(result, items) + assert result[0] == 3 + def test_missing_func(): - items = [1,2,3] - try: - pm.eval("(arr) => {arr.after()}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__('TypeError: arr.after is not a function') - -#reverse + items = [1, 2, 3] + try: + pm.eval("(arr) => {arr.after()}")(items) + assert False + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('TypeError: arr.after is not a function') + +# reverse + + def test_reverse(): - items = [1,2,3] - pm.eval("(arr) => {arr.reverse()}")(items) - assert items == [3,2,1] + items = [1, 2, 3] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [3, 2, 1] + def test_reverse_size_one(): - items = [1] - pm.eval("(arr) => {arr.reverse()}")(items) - assert items == [1] + items = [1] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [1] + def test_reverse_size_zero(): - items = [] - pm.eval("(arr) => {arr.reverse()}")(items) - assert items == [] + items = [] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [] + def test_reverse_returns_reference(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reverse(); result[0][0] = 4}")(result, items) - assert result[0] == [4,2,1] - assert items == [4,2,1] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reverse(); result[0][0] = 4}")(result, items) + assert result[0] == [4, 2, 1] + assert items == [4, 2, 1] + def test_reverse_ignores_extra_args(): - items = [1,2,3] - pm.eval("(arr) => {arr.reverse(9)}")(items) - assert items == [3,2,1] + items = [1, 2, 3] + pm.eval("(arr) => {arr.reverse(9)}")(items) + assert items == [3, 2, 1] + +# pop + -#pop def test_pop(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) - assert items == [1,2] - assert result[0] == 3 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) + assert items == [1, 2] + assert result[0] == 3 + def test_pop_empty(): - items = [] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) - assert items == [] - assert result[0] is None + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) + assert items == [] + assert result[0] is None + def test_pop_ignore_extra_args(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.pop(1)}")(result, items) - assert items == [1,2] - assert result[0] == 3 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop(1)}")(result, items) + assert items == [1, 2] + assert result[0] == 3 + +# join + -#join def test_join_no_arg(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == '1,2,3' + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,2,3' + def test_join_empty_array(): - items = [] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == '' + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '' + def test_join_no_arg_diff_types(): - items = [1,False,"END"] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == '1,false,END' + items = [1, False, "END"] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,false,END' + def test_join_no_arg_with_embedded_list_type(): - items = [1,[2,3],"END"] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == '1,2,3,END' + items = [1, [2, 3], "END"] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,2,3,END' + def test_join_with_sep(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join('-')}")(result, items) - assert result[0] == '1-2-3' + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join('-')}")(result, items) + assert result[0] == '1-2-3' + def test_join_none(): - items = [None,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == ',2,3' + items = [None, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == ',2,3' + def test_join_null(): - items = [pm.null,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == ',2,3' + items = [pm.null, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == ',2,3' + def test_join_utf8(): - prices = ["¥7", 500, 8123, 12] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.join()}")(result, prices) - assert result[0] == '¥7,500,8123,12' + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, prices) + assert result[0] == '¥7,500,8123,12' + +# toString + -#toString def test_toString(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) - assert result[0] == '1,2,3' - -#push + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) + assert result[0] == '1,2,3' + +# push + + def test_push(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.push(4)}")(result, items) - assert items == [1,2,3,4] - assert result[0] == 4 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push(4)}")(result, items) + assert items == [1, 2, 3, 4] + assert result[0] == 4 + def test_push_no_arg(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.push()}")(result, items) - assert items == [1,2,3,] - assert result[0] == 3 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push()}")(result, items) + assert items == [1, 2, 3,] + assert result[0] == 3 + def test_push_two_args(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.push(4,false)}")(result, items) - assert items == [1,2,3,4,False] - assert result[0] == 5 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push(4,false)}")(result, items) + assert items == [1, 2, 3, 4, False] + assert result[0] == 5 + def test_push_list(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.push([4,5])}")(result, items) - assert items == [1,2,3,[4,5]] - assert result[0] == 4 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push([4,5])}")(result, items) + assert items == [1, 2, 3, [4, 5]] + assert result[0] == 4 + +# shift + -#shift def test_shift(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) - assert items == [2,3] - assert result[0] == 1 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) + assert items == [2, 3] + assert result[0] == 1 + def test_shift_empty(): - items = [] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) - assert items == [] - assert result[0] is None + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) + assert items == [] + assert result[0] is None + +# unshift + -#unshift def test_unshift_zero_arg(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.unshift()}")(result, items) - assert items == [1,2,3] - assert result[0] == 3 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift()}")(result, items) + assert items == [1, 2, 3] + assert result[0] == 3 + def test_unshift_one_arg(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.unshift(6)}")(result, items) - assert items == [6,1,2,3] - assert result[0] == 4 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift(6)}")(result, items) + assert items == [6, 1, 2, 3] + assert result[0] == 4 + def test_unshift_two_args(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.unshift(6,7)}")(result, items) - assert items == [6,7,1,2,3] - assert result[0] == 5 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift(6,7)}")(result, items) + assert items == [6, 7, 1, 2, 3] + assert result[0] == 5 + +# concat + -#concat def test_concat_primitive(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.concat(4)}")(result, items) - assert items == [1,2,3] - assert result[0] == [1,2,3,4] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat(4)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [1, 2, 3, 4] + def test_concat_array(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.concat([4,5])}")(result, items) - assert items == [1,2,3] - assert result[0] == [1,2,3,4,5] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([4,5])}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [1, 2, 3, 4, 5] + def test_concat_empty_arg(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) - assert items == [1,2,3] - assert result[0] == [1,2,3] - assert items is not result[0] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [1, 2, 3] + assert items is not result[0] + def test_concat_two_arrays(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.concat([7,8], [0,1])}")(result, items) - assert items == [1,2,3] - assert result[0] == [1,2,3,7,8,0,1] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([7,8], [0,1])}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [1, 2, 3, 7, 8, 0, 1] + def test_concat_mix(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.concat([7,8], true, [0,1])}")(result, items) - assert items == [1,2,3] - assert result[0] == [1,2,3,7,8,True,0,1] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([7,8], true, [0,1])}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [1, 2, 3, 7, 8, True, 0, 1] + def test_concat_object_element(): - d = {"a":1} - items = [1, 2, d] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) - assert items == [1, 2, d] - assert result[0] == [1, 2, d] - assert items is not result[0] - assert d is items[2] - assert d is result[0][2] - -#slice + d = {"a": 1} + items = [1, 2, d] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) + assert items == [1, 2, d] + assert result[0] == [1, 2, d] + assert items is not result[0] + assert d is items[2] + assert d is result[0][2] + +# slice + + def test_slice(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.slice(1,2)}")(result, items) - assert items == [1,2,3] - assert result[0] == [2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,2)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [2] + def test_slice_copy(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.slice(0,3)}")(result, items) - assert items == [1,2,3] - assert result[0] == [1,2,3] - assert items is not result[0] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(0,3)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [1, 2, 3] + assert items is not result[0] + def test_slice_start_zero(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.slice(0,2)}")(result, items) - assert items == [1,2,3] - assert result[0] == [1,2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(0,2)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [1, 2] + def test_slice_stop_length(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.slice(1,3)}")(result, items) - assert items == [1,2,3] - assert result[0] == [2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,3)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [2, 3] + def test_slice_stop_beyond_length(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.slice(1,4)}")(result, items) - assert items == [1,2,3] - assert result[0] == [2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,4)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [2, 3] + def test_slice_start_negative(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.slice(-3,-1)}")(result, items) - assert result[0] == [1,2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(-3,-1)}")(result, items) + assert result[0] == [1, 2] + +# indexOf + -#indexOf def test_indexOf(): - items = [1,2,3,1] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) - assert result[0] == 0 + items = [1, 2, 3, 1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) + assert result[0] == 0 + def test_indexOf_with_start(): - items = [1,2,3,4,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(3, 3)}")(result, items) - assert result[0] == 4 + items = [1, 2, 3, 4, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(3, 3)}")(result, items) + assert result[0] == 4 + def test_indexOf_with_negative_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(3, -2)}")(result, items) - assert result[0] == 2 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(3, -2)}")(result, items) + assert result[0] == 2 + def test_indexOf_zero_size(): - items = [] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) - assert result[0] == -1 + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) + assert result[0] == -1 + def test_indexOf_start_beyond_length(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(1, 10)}")(result, items) - assert result[0] == -1 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1, 10)}")(result, items) + assert result[0] == -1 + def test_indexOf_not_found(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(10)}")(result, items) - assert result[0] == -1 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(10)}")(result, items) + assert result[0] == -1 + def test_indexOf_small_start(): - items = [1,2,3,1] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(1, -10)}")(result, items) - assert result[0] == 0 + items = [1, 2, 3, 1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1, -10)}")(result, items) + assert result[0] == 0 + +# lastIndexOf + -#lastIndexOf def test_lastIndexOf(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) - assert result[0] == 0 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) + assert result[0] == 0 + def test_lastIndexOf_dup(): - items = [1,2,3,1] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) - assert result[0] == 3 + items = [1, 2, 3, 1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) + assert result[0] == 3 + def test_lastIndexOf_with_from_index(): - items = [1,2,3,1] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 2)}")(result, items) - assert result[0] == 0 + items = [1, 2, 3, 1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 2)}")(result, items) + assert result[0] == 0 + def test_lastIndexOf_with_from_index_greater_than_size(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 10)}")(result, items) - assert result[0] == 0 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 10)}")(result, items) + assert result[0] == 0 + def test_lastIndexOf_with_negative_from_index(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -2)}")(result, items) - assert result[0] == 0 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -2)}")(result, items) + assert result[0] == 0 + def test_lastIndexOf_not_found(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(3, 0)}")(result, items) - assert result[0] == -1 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(3, 0)}")(result, items) + assert result[0] == -1 + def test_lastIndexOf_small_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -10)}")(result, items) - assert result[0] == -1 + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -10)}")(result, items) + assert result[0] == -1 + +# splice + -#splice def test_splice_no_args(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice()}")(result, items) - assert items == [1,2,3] - assert result[0] == [] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice()}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [] + def test_splice_one_arg(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1)}")(result, items) - assert items == [1] - assert result[0] == [2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1)}")(result, items) + assert items == [1] + assert result[0] == [2, 3] + def test_splice_one_arg_negative(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(-2)}")(result, items) - assert items == [1] - assert result[0] == [2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(-2)}")(result, items) + assert items == [1] + assert result[0] == [2, 3] + def test_splice_two_args_negative_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1, -1)}")(result, items) - assert items == [1,2,3] - assert result[0] == [] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, -1)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [] + def test_splice_two_args_zero_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1, 0)}")(result, items) - assert items == [1,2,3] - assert result[0] == [] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 0)}")(result, items) + assert items == [1, 2, 3] + assert result[0] == [] + def test_splice_two_args_one_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1, 1)}")(result, items) - assert items == [1,3] - assert result[0] == [2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 1)}")(result, items) + assert items == [1, 3] + assert result[0] == [2] + def test_splice_two_args_two_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1, 2)}")(result, items) - assert items == [1] - assert result[0] == [2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 2)}")(result, items) + assert items == [1] + assert result[0] == [2, 3] + def test_splice_three_args_zero_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5)}")(result, items) - assert items == [1,5,2,3] - assert result[0] == [] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5)}")(result, items) + assert items == [1, 5, 2, 3] + assert result[0] == [] + def test_splice_three_args_one_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5)}")(result, items) - assert items == [1,5,3] - assert result[0] == [2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5)}")(result, items) + assert items == [1, 5, 3] + assert result[0] == [2] + def test_splice_three_args_two_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5)}")(result, items) - assert items == [1,5] - assert result[0] == [2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5)}")(result, items) + assert items == [1, 5] + assert result[0] == [2, 3] + def test_splice_four_args_zero_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5,6)}")(result, items) - assert items == [1,5,6,2,3] - assert result[0] == [] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5,6)}")(result, items) + assert items == [1, 5, 6, 2, 3] + assert result[0] == [] + def test_splice_four_args_one_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5,6)}")(result, items) - assert items == [1,5,6,3] - assert result[0] == [2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5,6)}")(result, items) + assert items == [1, 5, 6, 3] + assert result[0] == [2] + def test_splice_four_args_two_count(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5,6)}")(result, items) - assert items == [1,5,6] - assert result[0] == [2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5,6)}")(result, items) + assert items == [1, 5, 6] + assert result[0] == [2, 3] + +# fill + -#fill def test_fill_returns_ref_to_self(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8)}")(result, items) - assert items == [8,8,8] - assert items is result[0] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8)}")(result, items) + assert items == [8, 8, 8] + assert items is result[0] + def test_fill_other_type(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(false)}")(result, items) - assert items == [False,False,False] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(false)}")(result, items) + assert items == [False, False, False] + def test_fill_with_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,1)}")(result, items) - assert items == [1,8,8] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1)}")(result, items) + assert items == [1, 8, 8] + def test_fill_with_start_negative(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,-2)}")(result, items) - assert items == [1,8,8] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,-2)}")(result, items) + assert items == [1, 8, 8] + def test_fill_with_start_too_high(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,7)}")(result, items) - assert items == [1,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,7)}")(result, items) + assert items == [1, 2, 3] + def test_fill_with_stop_smaller_than_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,7,2)}")(result, items) - assert items == [1,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,7,2)}")(result, items) + assert items == [1, 2, 3] + def test_fill_with_stop(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,1,2)}")(result, items) - assert items == [1,8,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,2)}")(result, items) + assert items == [1, 8, 3] + def test_fill_with_negative_stop(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-1)}")(result, items) - assert items == [1,8,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-1)}")(result, items) + assert items == [1, 8, 3] + def test_fill_with_negative_stop_too_low(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-10)}")(result, items) - assert items == [1,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-10)}")(result, items) + assert items == [1, 2, 3] + def test_fill_with_stop_too_high(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.fill(8,1,10)}")(result, items) - assert items == [1,8,8] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,10)}")(result, items) + assert items == [1, 8, 8] + def test_fill_object(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {let a = {a:1}; result[0] = arr.fill(a)}")(result, items) - assert items == [{"a":1},{"a":1},{"a":1}] - assert items is result[0] - assert items[0] is items[1] is items[2] - -#copyWithin + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {let a = {a:1}; result[0] = arr.fill(a)}")(result, items) + assert items == [{"a": 1}, {"a": 1}, {"a": 1}] + assert items is result[0] + assert items[0] is items[1] is items[2] + +# copyWithin + + def test_copyWithin(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(0,1)}")(result, items) - assert items == [2,3,3] - assert items is result[0] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(0,1)}")(result, items) + assert items == [2, 3, 3] + assert items is result[0] + def test_copyWithin_no_args(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin()}")(result, items) - assert items == [1,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin()}")(result, items) + assert items == [1, 2, 3] + def test_copyWithin_target_only_overwrite_all(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(0)}")(result, items) - assert items == [1,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(0)}")(result, items) + assert items == [1, 2, 3] + def test_copyWithin_target_only(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1)}")(result, items) - assert items == [1,1,2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1)}")(result, items) + assert items == [1, 1, 2] + def test_copyWithin_negative_target_only(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(-1)}")(result, items) - assert items == [1,2,1] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(-1)}")(result, items) + assert items == [1, 2, 1] + def test_copyWithin_target_too_large(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(10)}")(result, items) - assert items == [1,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(10)}")(result, items) + assert items == [1, 2, 3] + def test_copyWithin_target_and_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 2)}")(result, items) - assert items == [1,3,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 2)}")(result, items) + assert items == [1, 3, 3] + def test_copyWithin_target_and_start_too_large(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 10)}")(result, items) - assert items == [1,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 10)}")(result, items) + assert items == [1, 2, 3] + def test_copyWithin_target_and_negative_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -1)}")(result, items) - assert items == [1,3,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -1)}")(result, items) + assert items == [1, 3, 3] + def test_copyWithin_target_and_start_and_end(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,3)}")(result, items) - assert items == [1,3,3,4,5] + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,3)}")(result, items) + assert items == [1, 3, 3, 4, 5] + def test_copyWithin_target_and_start_and_negative_end(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-2)}")(result, items) - assert items == [1,3,3,4,5] + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-2)}")(result, items) + assert items == [1, 3, 3, 4, 5] + def test_copyWithin_target_too_small_and_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(-10,2)}")(result, items) - assert items == [3,2,3] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(-10,2)}")(result, items) + assert items == [3, 2, 3] + def test_copyWithin_target_greater_than_start(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => { result[0] = arr.copyWithin(2,1)}")(result, items) - assert items == [1,2,2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => { result[0] = arr.copyWithin(2,1)}")(result, items) + assert items == [1, 2, 2] + def test_copyWithin_target_and_start_too_small(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -10)}")(result, items) - assert items == [1,1,2] + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -10)}")(result, items) + assert items == [1, 1, 2] + def test_copyWithin_target_and_start_and_end_too_small(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-10)}")(result, items) - assert items == [1,2,3,4,5] + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-10)}")(result, items) + assert items == [1, 2, 3, 4, 5] + def test_copyWithin_target_and_start_and_end_too_large(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,1,10)}")(result, items) - assert items == [1,2,2,3,4] + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,1,10)}")(result, items) + assert items == [1, 2, 2, 3, 4] + def test_copyWithin_target_and_start_greater_than_end(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,3,2)}")(result, items) - assert items == [1,2,3,4,5] + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,3,2)}")(result, items) + assert items == [1, 2, 3, 4, 5] + +# includes + -#includes def test_includes(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes(1)}")(result, items) - assert result[0] == True + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1)}")(result, items) + assert result[0] + def test_includes_start_index(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes(1, 1)}")(result, items) - assert result[0] == False + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, 1)}")(result, items) + assert not result[0] + def test_includes_start_index_negative(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes(1, -1)}")(result, items) - assert result[0] == False + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, -1)}")(result, items) + assert not result[0] + def test_includes_start_index_negative_large(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes(1, -10)}")(result, items) - assert result[0] == True + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, -10)}")(result, items) + assert result[0] + def test_includes_start_index_large(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes(1, 10)}")(result, items) - assert result[0] == False + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, 10)}")(result, items) + assert not result[0] + def test_includes_other_type(): - items = [1,2,'Hi'] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes('Hi')}")(result, items) - assert result[0] == True + items = [1, 2, 'Hi'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes('Hi')}")(result, items) + assert result[0] + def test_includes_not(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes(5)}")(result, items) - assert result[0] == False + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(5)}")(result, items) + assert not result[0] + def test_includes_not_other_type(): - items = [1,2,'Hi'] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.includes('Joe')}")(result, items) - assert result[0] == False + items = [1, 2, 'Hi'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes('Joe')}")(result, items) + assert not result[0] + def test_includes_too_few_args(): - items = [4,2,6,7] - try: - pm.eval("(arr) => {arr.includes()}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: includes: At least 1 argument required, but only 0 passed") - -#sort + items = [4, 2, 6, 7] + try: + pm.eval("(arr) => {arr.includes()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: includes: At least 1 argument required, but only 0 passed") + +# sort + + def test_sort_empty(): - items = [] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) - assert result[0] is items - assert items == [] + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [] + def test_sort_one(): - items = [5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) - assert result[0] is items - assert items == [5] + items = [5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [5] + def test_sort_numbers(): - items = [4,2,6,7] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) - assert result[0] is items - assert items == [2,4,6,7] + items = [4, 2, 6, 7] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [2, 4, 6, 7] + def test_sort_strings(): - items = ['Four', 'Three', 'One'] - pm.eval("(arr) => {arr.sort()}")(items) - assert items == ['Four', 'One', 'Three'] + items = ['Four', 'Three', 'One'] + pm.eval("(arr) => {arr.sort()}")(items) + assert items == ['Four', 'One', 'Three'] + def test_sort_with_two_args_keyfunc(): - items = ['Four', 'Three', 'One'] - def myFunc(e, f): - return len(e) - len(f) - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) - assert items == ['One', 'Four', 'Three'] + items = ['Four', 'Three', 'One'] + + def myFunc(e, f): + return len(e) - len(f) + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert items == ['One', 'Four', 'Three'] + def test_sort_with_two_args_keyfunc_wrong_return_type(): - items = ['Four', 'Three', 'One'] - def myFunc(e,f): - return e + f - try: - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "incorrect compare function return type" - + items = ['Four', 'Three', 'One'] + + def myFunc(e, f): + return e + f + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "incorrect compare function return type" + + def test_sort_with_two_args_keyfunc_wrong_data_type(): - items = [4,2,6,7] - def myFunc(e,f): - return len(e) - len(f) - try: - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert "object of type 'float' has no len()" in str(e) - assert "test_arrays.py" in str(e) - assert "line 751" in str(e) - assert "in myFunc" in str(e) - assert "JS Stack Trace" in str(e) - assert "@evaluate:1:27" in str(e) + items = [4, 2, 6, 7] + + def myFunc(e, f): + return len(e) - len(f) + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert "object of type 'float' has no len()" in str(e) + assert "test_arrays.py" in str(e) + assert "line 883" in str(e) + assert "in myFunc" in str(e) + assert "JS Stack Trace" in str(e) + assert "@evaluate:1:27" in str(e) + def test_sort_with_one_arg_keyfunc(): - items = ['Four', 'Three', 'One'] - def myFunc(e): - return len(e) - try: - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert "takes 1 positional argument but 2 were given" in str(e) - assert "JS Stack Trace" in str(e) - assert "@evaluate:1:27" in str(e) + items = ['Four', 'Three', 'One'] + + def myFunc(e): + return len(e) + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert "takes 1 positional argument but 2 were given" in str(e) + assert "JS Stack Trace" in str(e) + assert "@evaluate:1:27" in str(e) + def test_sort_with_builtin_keyfunc(): - items = ['Four', 'Three', 'One'] - try: - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, len) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert "len() takes exactly one argument (2 given)" in str(e) - assert "JS Stack Trace" in str(e) - assert "@evaluate:1:27" in str(e) + items = ['Four', 'Three', 'One'] + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, len) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert "len() takes exactly one argument (2 given)" in str(e) + assert "JS Stack Trace" in str(e) + assert "@evaluate:1:27" in str(e) + def test_sort_with_js_func(): - items = ['Four', 'Three', 'One'] - result = [None] - myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") - pm.eval("(result, arr, compareFun) => {result[0] = arr.sort(compareFun)}")(result, items, myFunc) - assert result[0] is items - assert items == ['Four', 'One', 'Three'] + items = ['Four', 'Three', 'One'] + result = [None] + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + pm.eval("(result, arr, compareFun) => {result[0] = arr.sort(compareFun)}")(result, items, myFunc) + assert result[0] is items + assert items == ['Four', 'One', 'Three'] + def test_sort_numbers_tricky(): - items = [1, 30, 4, 21, 100000] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) - assert result[0] is items - assert items == [1, 100000, 21, 30, 4] + items = [1, 30, 4, 21, 100000] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [1, 100000, 21, 30, 4] + def test_sort_with_js_func_wrong_data_type(): - items = [4,2,6,7] - myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") - try: - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: a.toLocaleUpperCase is not a function") - -#forEach + items = [4, 2, 6, 7] + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: a.toLocaleUpperCase is not a function") + +# forEach + + def test_forEach(): - items = ['Four', 'Three', 'One'] - result = [''] - returnResult = [0] - pm.eval("(returnResult, result, arr) => {returnResult[0] = arr.forEach((element) => result[0] += element)}")(returnResult, result, items) - assert items == ['Four', 'Three', 'One'] - assert result == ['FourThreeOne'] - assert returnResult == [None] + items = ['Four', 'Three', 'One'] + result = [''] + returnResult = [0] + pm.eval(""" + (returnResult, result, arr) => { + returnResult[0] = arr.forEach((element) => result[0] += element); + } + """)(returnResult, result, items) + assert items == ['Four', 'Three', 'One'] + assert result == ['FourThreeOne'] + assert returnResult == [None] + def test_forEach_check_index(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.forEach((element, index) => result[0] += index)}")(result, items) - assert result == ['012'] + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.forEach((element, index) => result[0] += index)}")(result, items) + assert result == ['012'] + def test_forEach_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.forEach((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.forEach((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_forEach_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -856,53 +1003,74 @@ class Counter { )(result, items) assert result == [3] + def test_forEach_check_this_arg_wrong_type(): - items = ['Four', 'Three', 'One'] - result = [None] - a = 9 - try: - pm.eval("(result, arr, a) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, a);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items, a) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: 'this' argument is not an object or null") + items = ['Four', 'Three', 'One'] + result = [None] + a = 9 + try: + pm.eval(""" + (result, arr, a) => { + class Counter { + constructor() { + this.count = 0; + } + add(array) { + array.forEach(function countEntry(entry) { ++this.count; }, a); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """)(result, items, a) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: 'this' argument is not an object or null") # TODO python function support + def test_forEach_with_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.forEach(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [None] + def func(element, index, array): + array[int(index)] = "to each his own" + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.forEach(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [None] + def test_forEach_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.forEach(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_forEach_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 - + try: pm.eval(""" (arr, increment, result) => { @@ -914,11 +1082,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_forEach_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -932,46 +1101,68 @@ def test_forEach_self_jsfunction(): assert result == 3 # TODO should not pass + + def test_forEach_check_this_arg_null(): - items = ['Four', 'Three', 'One'] - result = [None] - try: - pm.eval("(result, arr) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, null);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: this is null") + items = ['Four', 'Three', 'One'] + result = [None] + try: + pm.eval(""" + (result, arr) => { + class Counter { + constructor() { + this.count = 0; + } + add(array) { + array.forEach(function countEntry(entry) {++this.count; }, null); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """)(result, items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: this is null") + def test_forEach_too_few_args(): - items = [4,2,6,7] - try: - pm.eval("(arr) => {arr.forEach()}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: forEach: At least 1 argument required, but only 0 passed") - -#map + items = [4, 2, 6, 7] + try: + pm.eval("(arr) => {arr.forEach()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: forEach: At least 1 argument required, but only 0 passed") + +# map + + def test_map(): - items = [4,2,6,7] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.map((x) => x * x)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == [16,4,36,49] + items = [4, 2, 6, 7] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.map((x) => x * x)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == [16, 4, 36, 49] + def test_map_check_index(): - items = [4,2,6,7] - result = [''] - pm.eval("(result, arr) => {arr.map((x, index) => result[0] += index)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == '0123' + items = [4, 2, 6, 7] + result = [''] + pm.eval("(result, arr) => {arr.map((x, index) => result[0] += index)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == '0123' + def test_map_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.map((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.map((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_map_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -994,68 +1185,77 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + def test_map_too_few_args(): - items = [4,2,6,7] - try: - pm.eval("(arr) => {arr.map()}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: map: At least 1 argument required, but only 0 passed") + items = [4, 2, 6, 7] + try: + pm.eval("(arr) => {arr.map()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: map: At least 1 argument required, but only 0 passed") + def test_map_arg_wrong_type(): - items = [4,2,6,7] - try: - pm.eval("(arr) => {arr.map(8)}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: map: callback is not a function") + items = [4, 2, 6, 7] + try: + pm.eval("(arr) => {arr.map(8)}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: map: callback is not a function") + def test_map_check_array_mutation(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.map((element, index, array) => {array[0] = 'Ten'; result[0] = array})}")(result, items) - assert result[0] == ['Ten', 'Three', 'One'] - assert items == ['Ten', 'Three', 'One'] + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.map((element, index, array) => {array[0] = 'Ten'; result[0] = array})}")(result, items) + assert result[0] == ['Ten', 'Three', 'One'] + assert items == ['Ten', 'Three', 'One'] + def test_map_with_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - return 42 - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.map(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [[42.0, 42.0, 42.0]] + def func(element, index, array): + array[int(index)] = "to each his own" + return 42 + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.map(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [[42.0, 42.0, 42.0]] + def test_map_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.map(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_map_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 - + try: pm.eval(""" (arr, increment, result) => { @@ -1067,11 +1267,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_map_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -1082,29 +1283,34 @@ def test_map_self_jsfunction(): return jsObj.count; } """)(items) - assert result == 3 - -#filter + assert result == 3 + +# filter + + def test_filter(): - words = ['spray', 'elite', 'exuberant', 'destruction', 'present'] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.filter((word) => word.length > 6)}")(result, words) - assert words == ['spray', 'elite', 'exuberant', 'destruction', 'present'] - assert result[0] == ['exuberant', 'destruction', 'present'] + words = ['spray', 'elite', 'exuberant', 'destruction', 'present'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.filter((word) => word.length > 6)}")(result, words) + assert words == ['spray', 'elite', 'exuberant', 'destruction', 'present'] + assert result[0] == ['exuberant', 'destruction', 'present'] + def test_filter_check_index(): - items = [4,2,6,7] - result = [''] - pm.eval("(result, arr) => {arr.filter((x, index) => result[0] += index)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == '0123' + items = [4, 2, 6, 7] + result = [''] + pm.eval("(result, arr) => {arr.filter((x, index) => result[0] += index)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == '0123' + def test_filter_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.filter((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.filter((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_filter_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -1127,51 +1333,58 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + def test_filter_too_few_args(): - items = [4,2,6,7] - try: - pm.eval("(arr) => {arr.filter()}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") + items = [4, 2, 6, 7] + try: + pm.eval("(arr) => {arr.filter()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") + def test_filter_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.filter(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [[]] + def func(element, index, array): + array[int(index)] = "to each his own" + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.filter(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [[]] + def test_filter_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.filter(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_filter_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 - + try: pm.eval(""" (arr, increment, result) => { @@ -1183,11 +1396,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_filter_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -1198,140 +1412,226 @@ def test_filter_self_jsfunction(): return jsObj.count; } """)(items) - assert result == 3 + assert result == 3 + +# reduce + -#reduce def test_reduce(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0)}")(result, items) - assert items == [1,2,3,4,5] - assert result[0] == 15 + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); + } + """)(result, items) + assert items == [1, 2, 3, 4, 5] + assert result[0] == 15 + def test_reduce_empty_array_no_accumulator(): - items = [] - try: - pm.eval("(arr) => {arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("TypeError: reduce of empty array with no initial value") + items = [] + try: + pm.eval("(arr) => {arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: reduce of empty array with no initial value") + def test_reduce_float(): - items = [1.9, 4.6, 9.3, 16.5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0)}")(result, items) - assert result[0] == 32.3 + items = [1.9, 4.6, 9.3, 16.5] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); + } + """)(result, items) + assert result[0] == 32.3 + def test_reduce_string(): - items = ['Hi', 'There'] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, "")}")(result, items) - assert result[0] == 'HiThere' + items = ['Hi', 'There'] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, ""); + } + """)(result, items) + assert result[0] == 'HiThere' + def test_reduce_initial_value_not_zero(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 5)}")(result, items) - assert items == [1,2,3,4,5] - assert result[0] == 20 + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 5); + } + """)(result, items) + assert items == [1, 2, 3, 4, 5] + assert result[0] == 20 + + +def test_reduce_no_initial_value(): + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue); + } + """)(result, items) + assert items == [1, 2, 3, 4, 5] + assert result[0] == 15 -def test_reduce_no_initial_value(): - items = [1,2,3,4,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(result, items) - assert items == [1,2,3,4,5] - assert result[0] == 15 def test_reduce_length_one_with_initial_value(): - items = [1] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 2)}")(result, items) - assert result[0] == 3 + items = [1] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 2); + } + """)(result, items) + assert result[0] == 3 + def test_reduce_length_one_no_initial_value(): - items = [1] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(result, items) - assert result[0] == 1 + items = [1] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue); + } + """)(result, items) + assert result[0] == 1 + def test_reduce_list_meaningless(): - items = [['Hi', 'There']] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduce((x) => x * 2)}")(result, items) - assert result[0] == ['Hi', 'There'] + items = [['Hi', 'There']] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((x) => x * 2)}")(result, items) + assert result[0] == ['Hi', 'There'] + +# reduceRight + -#reduceRight def test_reduceRight_list_concat(): - items = [[0, 1],[2, 3],[4, 5]] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue))}")(result, items) - assert result[0] == [4, 5, 2, 3, 0, 1] + items = [[0, 1], [2, 3], [4, 5]] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue)); + } + """)(result, items) + assert result[0] == [4, 5, 2, 3, 0, 1] + def test_reduceRight_list_concat_with_initial_value(): - items = [[0, 1],[2, 3],[4, 5]] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue), [7,8])}")(result, items) - assert result[0] == [7, 8, 4, 5, 2, 3, 0, 1] + items = [[0, 1], [2, 3], [4, 5]] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue), [7,8]); + } + """)(result, items) + assert result[0] == [7, 8, 4, 5, 2, 3, 0, 1] + def test_reduceRight(): - items = [0,1,2,3,4] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) - assert result[0] == 10 + items = [0, 1, 2, 3, 4] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue); + } + """)(result, items) + assert result[0] == 10 + def test_reduceRight_with_initial_value(): - items = [0,1,2,3,4] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue, 5)}")(result, items) - assert result[0] == 15 + items = [0, 1, 2, 3, 4] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue, 5); + } + """)(result, items) + assert result[0] == 15 + def test_reduceRight_float(): - items = [1.9, 4.6, 9.3, 16.5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) - assert result[0] == 32.3 + items = [1.9, 4.6, 9.3, 16.5] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue); + } + """)(result, items) + assert result[0] == 32.3 + def test_reduceRight_check_index(): - items = [1.9, 4.6, 9.3, 16.5] - result = [''] - pm.eval("(result, arr) => {arr.reduceRight((accumulator, currentValue, index, array) => {accumulator + currentValue; result[0] += index})}")(result, items) - assert result[0] == '210' + items = [1.9, 4.6, 9.3, 16.5] + result = [''] + pm.eval(""" + (result, arr) => { + arr.reduceRight((accumulator, currentValue, index, array) => { + accumulator + currentValue; + result[0] += index + }); + } + """)(result, items) + assert result[0] == '210' + def test_reduceRight_check_array(): - items = ['Four', 'Three', 'One'] - result = [None] - pm.eval("(result, arr) => {arr.reduceRight((accumulator, currentValue, index, array) => {accumulator + currentValue; result[0] = array})}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval(""" + (result, arr) => { + arr.reduceRight((accumulator, currentValue, index, array) => { + accumulator + currentValue; + result[0] = array; + }); + } + """)(result, items) + assert result == [items] + assert result[0] is items + +# some + -#some def test_some_true(): - items = [1, 2, 3, 4, 5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) - assert items == [1, 2, 3, 4, 5] - assert result[0] == True + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) + assert items == [1, 2, 3, 4, 5] + assert result[0] + def test_some_false(): - items = [1,3,5] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) - assert result[0] == False + items = [1, 3, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) + assert not result[0] + def test_some_check_index(): - items = [4,2,6,7] - result = [''] - pm.eval("(result, arr) => {arr.some((x, index) => result[0] += index)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == '0123' + items = [4, 2, 6, 7] + result = [''] + pm.eval("(result, arr) => {arr.some((x, index) => result[0] += index)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == '0123' + def test_some_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.some((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.some((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_some_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -1354,62 +1654,69 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + def test_some_truthy_conversion(): - result = [None] - pm.eval( - """ + result = [None] + pm.eval( + """ (result) => { const TRUTHY_VALUES = [true, "true", 1, {}]; function getBoolean(value) { if (typeof value === "string") { value = value.toLowerCase().trim(); - } + } return TRUTHY_VALUES.some((t) => t === value); } result[0] = getBoolean(1); } """)(result) - assert result[0] == True + assert result[0] + def test_some_with_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - return False - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.some(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [False] + def func(element, index, array): + array[int(index)] = "to each his own" + return False + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.some(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [False] + def test_some_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - return False - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + return False + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.some(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_some_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 return False - + try: pm.eval(""" (arr, increment, result) => { @@ -1421,11 +1728,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_some_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -1437,35 +1745,41 @@ def test_some_self_jsfunction(): return jsObj.count; } """)(items) - assert result == 3 + assert result == 3 + +# every + -#every def test_every_true(): - items = [2,4,6] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) - assert items == [2,4,6] - assert result[0] == True + items = [2, 4, 6] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) + assert items == [2, 4, 6] + assert result[0] + def test_every_false(): - items = [1,2,4,6] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) - assert result[0] == False + items = [1, 2, 4, 6] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) + assert not result[0] + def test_every_check_index(): - items = [4,2,6,7] - result = [''] - pm.eval("(result, arr) => {arr.every((x, index) => result[0] += index)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == '0' + items = [4, 2, 6, 7] + result = [''] + pm.eval("(result, arr) => {arr.every((x, index) => result[0] += index)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == '0' + def test_every_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.every((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.every((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_every_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -1488,44 +1802,50 @@ class Counter { } """ )(result, items) - assert result == [1] + assert result == [1] + def test_every_with_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - return True - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.every(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [True] + def func(element, index, array): + array[int(index)] = "to each his own" + return True + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.every(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [True] + def test_every_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - return True - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + return True + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.every(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_every_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 - + try: pm.eval(""" (arr, increment, result) => { @@ -1537,11 +1857,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_every_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -1555,39 +1876,46 @@ def test_every_self_jsfunction(): """)(items) assert result == 3 -#find +# find + + def test_find_found_once(): - items = [5, 12, 8, 130, 44] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 100)}")(result, items) - assert items == [5, 12, 8, 130, 44] - assert result[0] == 130 + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 100)}")(result, items) + assert items == [5, 12, 8, 130, 44] + assert result[0] == 130 + def test_find_found_twice(): - items = [5, 12, 8, 130, 4] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 10)}")(result, items) - assert result[0] == 12 + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 10)}")(result, items) + assert result[0] == 12 + def test_find_not_found(): - items = [5, 12, 8, 130, 44] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 1000)}")(result, items) - assert result[0] is None + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 1000)}")(result, items) + assert result[0] is None + def test_find_check_index(): - items = [4,2,6,7] - result = [''] - pm.eval("(result, arr) => {arr.find((x, index) => result[0] += index)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == '0123' + items = [4, 2, 6, 7] + result = [''] + pm.eval("(result, arr) => {arr.find((x, index) => result[0] += index)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == '0123' + def test_find_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.find((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.find((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_find_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -1610,45 +1938,51 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + def test_find_with_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - return False - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.find(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [None] + def func(element, index, array): + array[int(index)] = "to each his own" + return False + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.find(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [None] + def test_find_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - return False - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + return False + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.find(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_find_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 return False - + try: pm.eval(""" (arr, increment, result) => { @@ -1660,11 +1994,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_find_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -1676,41 +2011,48 @@ def test_find_self_jsfunction(): return jsObj.count; } """)(items) - assert result == 3 + assert result == 3 + +# findIndex + -#findIndex def test_findIndex_found_once(): - items = [5, 12, 8, 130, 44] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 100)}")(result, items) - assert items == [5, 12, 8, 130, 44] - assert result[0] == 3 + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 100)}")(result, items) + assert items == [5, 12, 8, 130, 44] + assert result[0] == 3 + def test_findIndex_found_twice(): - items = [5, 12, 8, 130, 4] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 10)}")(result, items) - assert result[0] == 1 + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 10)}")(result, items) + assert result[0] == 1 + def test_findIndex_not_found(): - items = [5, 12, 8, 130, 4] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 1000)}")(result, items) - assert result[0] == -1 + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 1000)}")(result, items) + assert result[0] == -1 + def test_findIndex_check_index(): - items = [4,2,6,7] - result = [''] - pm.eval("(result, arr) => {arr.findIndex((x, index) => result[0] += index)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == '0123' + items = [4, 2, 6, 7] + result = [''] + pm.eval("(result, arr) => {arr.findIndex((x, index) => result[0] += index)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == '0123' + def test_findIndex_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.findIndex((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.findIndex((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_findIndex_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -1733,42 +2075,48 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + def test_findIndex_with_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.findIndex(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [-1] + def func(element, index, array): + array[int(index)] = "to each his own" + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.findIndex(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [-1] + def test_findIndex_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.findIndex(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_findIndex_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 - + try: pm.eval(""" (arr, increment, result) => { @@ -1780,11 +2128,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_findIndex_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -1795,99 +2144,116 @@ def test_findIndex_self_jsfunction(): return jsObj.count; } """)(items) - assert result == 3 + assert result == 3 + +# flat + -#flat def test_flat(): - items = [0, 1, 2, [3, 4]] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flat()}")(result, items) - assert items == [0, 1, 2, [3, 4]] - assert result[0] == [0, 1, 2, 3, 4] + items = [0, 1, 2, [3, 4]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat()}")(result, items) + assert items == [0, 1, 2, [3, 4]] + assert result[0] == [0, 1, 2, 3, 4] + def test_flat_with_js_array(): - items = [0, 1, 2, [3, 4]] - result = [0] - pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flat()}")(result, items) - assert items == [0, [10, 11], 2, [3, 4]] - assert result[0] == [0, 10, 11, 2, 3, 4] + items = [0, 1, 2, [3, 4]] + result = [0] + pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flat()}")(result, items) + assert items == [0, [10, 11], 2, [3, 4]] + assert result[0] == [0, 10, 11, 2, 3, 4] + def test_flat_depth_zero(): - items = [0, 1, [2, [3, [4, 5]]]] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flat(0)}")(result, items) - assert result[0] == [0, 1, [2, [3, [4, 5]]]] + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(0)}")(result, items) + assert result[0] == [0, 1, [2, [3, [4, 5]]]] + def test_flat_depth_one(): - items = [0, 1, [2, [3, [4, 5]]]] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flat(1)}")(result, items) - assert items == [0, 1, [2, [3, [4, 5]]]] - assert result[0] == [0, 1, 2, [3, [4, 5]]] + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(1)}")(result, items) + assert items == [0, 1, [2, [3, [4, 5]]]] + assert result[0] == [0, 1, 2, [3, [4, 5]]] + def test_flat_depth_two(): - items = [0, 1, [2, [3, [4, 5]]]] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flat(2)}")(result, items) - assert items == [0, 1, [2, [3, [4, 5]]]] - assert result[0] == [0, 1, 2, 3, [4, 5]] + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(2)}")(result, items) + assert items == [0, 1, [2, [3, [4, 5]]]] + assert result[0] == [0, 1, 2, 3, [4, 5]] + def test_flat_depth_large(): - items = [0, 1, [2, [3, [4, 5]]]] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flat(10)}")(result, items) - assert result[0] == [0, 1, 2, 3, 4, 5] + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(10)}")(result, items) + assert result[0] == [0, 1, 2, 3, 4, 5] + +# flatMap + -#flatMap def test_flatMap(): - items = [1, 2, 1] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) - assert items == [1,2,1] - assert result[0] == [1,2,2,1] + items = [1, 2, 1] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1, 2, 1] + assert result[0] == [1, 2, 2, 1] + def test_flatMap_with_js_array(): - items = [1,2,2,1] - result = [0] - pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) - assert items == [1, [10, 11], 2, 1] - assert result[0] == [1, 1, 2, 2, 1] + items = [1, 2, 2, 1] + result = [0] + pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")( + result, items) + assert items == [1, [10, 11], 2, 1] + assert result[0] == [1, 1, 2, 2, 1] + def test_flatMap_no_replace(): - items = [1,2,[4,5]] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) - assert items == [1, 2, [4, 5]] - assert result[0] == [1, 2, 2, 1] + items = [1, 2, [4, 5]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1, 2, [4, 5]] + assert result[0] == [1, 2, 2, 1] + def test_flatMap_no_replace_depth_one(): - items = [1,2,[4,5]] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, [2, 2]] : 1))}")(result, items) - assert items == [1, 2, [4, 5]] - assert result[0] == [1, 2, [2, 2], 1] - + items = [1, 2, [4, 5]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, [2, 2]] : 1))}")(result, items) + assert items == [1, 2, [4, 5]] + assert result[0] == [1, 2, [2, 2], 1] + + def test_flatMap_equivalence(): - items = [1, 2, 1] - result = [0] - result2 = [0] - pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) - pm.eval("(result, arr) => {result[0] = arr.map((num) => (num === 2 ? [2, 2] : 1)).flat()}")(result2, items) - assert result[0] == result2[0] + items = [1, 2, 1] + result = [0] + result2 = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + pm.eval("(result, arr) => {result[0] = arr.map((num) => (num === 2 ? [2, 2] : 1)).flat()}")(result2, items) + assert result[0] == result2[0] + def test_flatMap_check_index(): - items = [4,2,6,7] - result = [''] - pm.eval("(result, arr) => {arr.flatMap((x, index) => result[0] += index)}")(result, items) - assert items == [4,2,6,7] - assert result[0] == '0123' + items = [4, 2, 6, 7] + result = [''] + pm.eval("(result, arr) => {arr.flatMap((x, index) => result[0] += index)}")(result, items) + assert items == [4, 2, 6, 7] + assert result[0] == '0123' + def test_flatMap_check_array(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.flatMap((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] - assert result[0] is items + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.flatMap((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + def test_flatMap_check_this_arg(): items = ['Four', 'Three', 'One'] @@ -1910,43 +2276,49 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + def test_flatMap_with_python_function(): - def func(element, index, array): - array[int(index)] = "to each his own" - return 42 - items = ['Four', 'Three', 'One'] - returnResult = [0] - pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.flatMap(func)}")(returnResult, items, func) - assert items == ['to each his own', 'to each his own', 'to each his own'] - assert returnResult == [[42.0, 42.0, 42.0]] + def func(element, index, array): + array[int(index)] = "to each his own" + return 42 + items = ['Four', 'Three', 'One'] + returnResult = [0] + pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.flatMap(func)}")(returnResult, items, func) + assert items == ['to each his own', 'to each his own', 'to each his own'] + assert returnResult == [[42.0, 42.0, 42.0]] + def test_flatMap_self_pymethod(): - items = ['Four', 'Three', 'One'] - class Counter: - def __init__(self): - self.count = 0 - def increment(self, element, index, array): - self.count += 1 - - obj = Counter() - assert obj.count == 0 - result = pm.eval(""" + items = ['Four', 'Three', 'One'] + + class Counter: + def __init__(self): + self.count = 0 + + def increment(self, element, index, array): + self.count += 1 + + obj = Counter() + assert obj.count == 0 + result = pm.eval(""" (arr, increment, result) => { let jsObj = {count: 0} arr.flatMap(increment, jsObj); return jsObj.count; } """)(items, obj.increment) - assert obj.count == 0 - assert result == 3 + assert obj.count == 0 + assert result == 3 + def test_flatMap_self_pyfunction(): items = ['Four', 'Three', 'One'] + def increment(self, element, index, array): self.count += 1 - + try: pm.eval(""" (arr, increment, result) => { @@ -1958,11 +2330,12 @@ def increment(self, element, index, array): assert False except Exception as e: assert type(e) is TypeError - assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + assert str(e).__contains__("unbound python functions do not have a 'self' to bind") + def test_flatMap_self_jsfunction(): items = ['Four', 'Three', 'One'] - + result = pm.eval(""" (arr) => { function increment(element, index, array) { @@ -1973,182 +2346,263 @@ def test_flatMap_self_jsfunction(): return jsObj.count; } """)(items) - assert result == 3 + assert result == 3 + +# valueOf + -#valueOf def test_valueOf(): - items = [1, 2, 1] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.valueOf()}")(result, items) - assert items == [1,2,1] - assert result[0] is items + items = [1, 2, 1] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.valueOf()}")(result, items) + assert items == [1, 2, 1] + assert result[0] is items + +# toLocaleString + -#toLocaleString def test_toLocaleString(): - prices = ["¥7", 500, 8123, 12] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) - assert result[0] == '¥7,¥500,¥8,123,¥12' + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' }); + } + """)(result, prices) + assert result[0] == '¥7,¥500,¥8,123,¥12' + def test_toLocaleString_with_none(): - prices = ["¥7", 500, 8123, None] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) - assert result[0] == '¥7,¥500,¥8,123,' + prices = ["¥7", 500, 8123, None] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' }); + } + """)(result, prices) + assert result[0] == '¥7,¥500,¥8,123,' + def test_toLocaleString_with_null(): - prices = ["¥7", 500, 8123, pm.null] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) - assert result[0] == '¥7,¥500,¥8,123,' + prices = ["¥7", 500, 8123, pm.null] + result = [None] + pm.eval(""" + (result, arr) => { + result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' }); + } + """)(result, prices) + assert result[0] == '¥7,¥500,¥8,123,' + def test_toLocaleString_no_args(): - prices = ["¥7", 500, 8123, 12] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toLocaleString()}")(result, prices) - assert result[0] == '¥7,500,8,123,12' + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString()}")(result, prices) + assert result[0] == '¥7,500,8,123,12' + def test_toLocaleString_one_arg_(): - prices = ["¥7", 500, 8123, 12] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP')}")(result, prices) - assert result[0] == '¥7,500,8,123,12' + prices = ["¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP')}")(result, prices) + assert result[0] == '¥7,500,8,123,12' + def test_toLocaleString_one_arg_invalid_locale(): - prices = ["¥7", 500, 8123, 12] - result = [None] - try: - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('asfasfsafsdf')}")(result, prices) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("RangeError: invalid language tag:") + prices = ["¥7", 500, 8123, 12] + result = [None] + try: + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('asfasfsafsdf')}")(result, prices) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("RangeError: invalid language tag:") + def test_toLocaleString_two_args_invalid_currency(): - prices = ["¥7", 500, 8123, 12] - result = [None] - try: - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPYsdagasfgas' })}")(result, prices) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("RangeError: invalid currency code in NumberFormat():") + prices = ["¥7", 500, 8123, 12] + result = [None] + try: + pm.eval(""" + (result, arr) => { + result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPYsdagasfgas' }); + } + """)(result, prices) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("RangeError: invalid currency code in NumberFormat():") + def test_toLocaleString_with_datetime(): - prices = [500, datetime(year=2020, month=1, day=31, hour=13, minute=14, second=31)] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('en-uk')}")(result, prices) - assert result[0] == '500,31/01/2020, 13:14:31' + prices = [500, datetime(year=2020, month=1, day=31, hour=13, minute=14, second=31)] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('en-uk')}")(result, prices) + assert result[0] == '500,31/01/2020, 13:14:31' + +# entries + -#entries def test_entries_next(): - items = ['a', 'b', 'c'] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.entries(); result[0] = result[0].next().value}")(result, items) - assert items == ['a', 'b', 'c'] - assert result[0] == [0, 'a'] + items = ['a', 'b', 'c'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0] = result[0].next().value}")(result, items) + assert items == ['a', 'b', 'c'] + assert result[0] == [0, 'a'] + def test_entries_next_next(): - items = ['a', 'b', 'c'] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) - assert result[0] == [1, 'b'] + items = ['a', 'b', 'c'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")( + result, items) + assert result[0] == [1, 'b'] + def test_entries_next_next_undefined(): - items = ['a'] - result = [0] - pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) - assert result[0] == None + items = ['a'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")( + result, items) + assert result[0] is None + +# keys + -#keys def test_keys_iterator(): - items = ['a', 'b', 'c'] - result = [7,8,9] - pm.eval("(result, arr) => { index = 0; iterator = arr.keys(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) - assert result == [0,1,2] + items = ['a', 'b', 'c'] + result = [7, 8, 9] + pm.eval(""" + (result, arr) => { + index = 0; + iterator = arr.keys(); + for (const key of iterator) { + result[index] = key; + index++; + } + } + """)(result, items) + assert result == [0, 1, 2] + +# values + -#values def test_values_iterator(): - items = ['a', 'b', 'c'] - result = [7,8,9] - pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const value of iterator) { result[index] = value; index++;} }")(result, items) - assert result == ['a', 'b', 'c'] - assert result is not items - -#constructor property + items = ['a', 'b', 'c'] + result = [7, 8, 9] + pm.eval(""" + (result, arr) => { + index = 0; + iterator = arr.values(); + for (const value of iterator) { + result[index] = value; + index++; + } + } + """)(result, items) + assert result == ['a', 'b', 'c'] + assert result is not items + +# constructor property + + def test_constructor_creates_array(): - items = [1,2] - result = [0] - pm.eval("(result, arr) => { result[0] = arr.constructor; result[0] = new result[0]; result[0][0] = 9}")(result, items) - assert result[0] == [9] + items = [1, 2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr.constructor; result[0] = new result[0]; result[0][0] = 9}")(result, items) + assert result[0] == [9] + +# length property + -#length property def test_constructor_creates_array(): - items = [1,2] - result = [0] - pm.eval("(result, arr) => { result[0] = arr.length}")(result, items) - assert result[0] == 2 + items = [1, 2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr.length}")(result, items) + assert result[0] == 2 + +# iterator symbol property + -#iterator symbol property def test_iterator_type_function(): - items = [1,2] - result = [0] - pm.eval("(result, arr) => { result[0] = typeof arr[Symbol.iterator]}")(result, items) - assert result[0] == 'function' + items = [1, 2] + result = [0] + pm.eval("(result, arr) => { result[0] = typeof arr[Symbol.iterator]}")(result, items) + assert result[0] == 'function' + def test_iterator_first_next(): - items = [1,2] - result = [0] - pm.eval("(result, arr) => { result[0] = arr[Symbol.iterator]().next()}")(result, items) - assert result[0].value == 1 - assert result[0].done == False + items = [1, 2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr[Symbol.iterator]().next()}")(result, items) + assert result[0].value == 1 + assert not result[0].done + def test_iterator_second_next(): - items = [1,2] - result = [0] - pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); result[0] = iterator.next()}")(result, items) - assert result[0].value == 2 - assert result[0].done == False + items = [1, 2] + result = [0] + pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); result[0] = iterator.next()}")( + result, items) + assert result[0].value == 2 + assert not result[0].done + def test_iterator_last_next(): - items = [1,2] - result = [0] - pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); iterator.next(); result[0] = iterator.next()}")(result, items) - assert result[0].value == None - assert result[0].done == True + items = [1, 2] + result = [0] + pm.eval(""" + (result, arr) => { + let iterator = arr[Symbol.iterator](); + iterator.next(); + iterator.next(); + result[0] = iterator.next(); + } + """)(result, items) + assert result[0].value is None + assert result[0].done + def test_iterator_iterator(): - items = [1,2,3,4] - result = [0] - pm.eval("(result, arr) => {let iter = arr[Symbol.iterator](); let head = iter.next().value; result[0] = [...iter] }")(result, items) - assert result[0] == [2,3,4] + items = [1, 2, 3, 4] + result = [0] + pm.eval("(result, arr) => {let iter = arr[Symbol.iterator](); let head = iter.next().value; result[0] = [...iter] }")( + result, items) + assert result[0] == [2, 3, 4] + +# Array.from + -#Array.from def test_array_from(): - items = [1,2] - result = [0] - pm.eval("(result, arr) => { result[0] = Array.from(arr)}")(result, items) - assert result[0] == [1,2] - assert result[0] is not items + items = [1, 2] + result = [0] + pm.eval("(result, arr) => { result[0] = Array.from(arr)}")(result, items) + assert result[0] == [1, 2] + assert result[0] is not items # bad index size expansion + + def test_assign_bad_index(): - result = [] - pm.eval("(result) => {result[0] = 4}")(result) - assert result[0] == 4 + result = [] + pm.eval("(result) => {result[0] = 4}")(result) + assert result[0] == 4 + def test_assign_bad_index_with_existing_next(): - result = [8] - pm.eval("(result) => {result[1] = 4}")(result) - assert result == [8,4] + result = [8] + pm.eval("(result) => {result[1] = 4}")(result) + assert result == [8, 4] + def test_assign_bad_index_with_gap(): - result = [] - pm.eval("(result) => {result[0] = 4; result[5] = 6}")(result) - assert result == [4, None, None, None, None, 6] + result = [] + pm.eval("(result) => {result[0] = 4; result[5] = 6}")(result) + assert result == [4, None, None, None, None, 6] + def test_array_subclass_behaves_as_array(): - my_JS_function = pm.eval(""" + my_JS_function = pm.eval(""" () => { class MyClass extends Array { constructor(...args) @@ -2160,52 +2614,65 @@ class MyClass extends Array { return new MyClass(1,2); } """) - - a = my_JS_function() - assert a == [1,2] - result = [] - for i in a: - result.append(i) - assert result == [1,2] - assert a is not result + + a = my_JS_function() + assert a == [1, 2] + result = [] + for i in a: + result.append(i) + assert result == [1, 2] + assert a is not result + def test_iter_operator_tuple(): - myit = iter((1, 2)) - result = [None, None, None] - pm.eval('(result, myit) => { result[0] = myit.next(); result[1] = myit.next(); result[2] = myit.next()}')(result, myit) - assert result[0] == {'done': False, 'value': 1.0} - assert result[1] == {'done': False, 'value': 2.0} - assert result[2] == {'done': True} + myit = iter((1, 2)) + result = [None, None, None] + pm.eval('(result, myit) => { result[0] = myit.next(); result[1] = myit.next(); result[2] = myit.next()}')( + result, myit) + assert result[0] == {'done': False, 'value': 1.0} + assert result[1] == {'done': False, 'value': 2.0} + assert result[2] == {'done': True} + def test_iter_operator_array(): - myit = iter([1, 2, 3]) - result = [None, None, None, None] - pm.eval('(result, myit) => { result[0] = myit.next(); result[1] = myit.next(); result[2] = myit.next(); result[3] = myit.next()}')(result, myit) - assert result[0] == {'done': False, 'value': 1.0} - assert result[1] == {'done': False, 'value': 2.0} - assert result[2] == {'done': False, 'value': 3.0} - assert result[3] == {'done': True} + myit = iter([1, 2, 3]) + result = [None, None, None, None] + pm.eval(""" + (result, myit) => { + result[0] = myit.next(); + result[1] = myit.next(); + result[2] = myit.next(); + result[3] = myit.next(); + } + """)(result, myit) + assert result[0] == {'done': False, 'value': 1.0} + assert result[1] == {'done': False, 'value': 2.0} + assert result[2] == {'done': False, 'value': 3.0} + assert result[3] == {'done': True} + def test_iter_reentrance(): - myit = iter((1,2)) - result = pm.eval("(iter) => iter")(myit) - assert myit is result + myit = iter((1, 2)) + result = pm.eval("(iter) => iter")(myit) + assert myit is result + def test_iter_reentrace_next(): - myit = iter((1, 2)) - result = [None] - pm.eval("(result, arr) => {result[0] = arr}")(result, myit) - next(result[0]) == 1 - next(result[0]) == 2 - try: - third = next(result[0]) - assert (False) - except StopIteration as e: - assert (True) + myit = iter((1, 2)) + result = [None] + pm.eval("(result, arr) => {result[0] = arr}")(result, myit) + next(result[0]) == 1 + next(result[0]) == 2 + try: + third = next(result[0]) + assert (False) + except StopIteration as e: + assert (True) + def test_iter_for_of(): - myit = iter((1,2)) - result = [None, None] - pm.eval("""(result, myit) => {let index = 0; for (const value of myit) {result[index++] = value}}""")(result, myit) - assert result[0] == 1.0 - assert result[1] == 2.0 \ No newline at end of file + myit = iter((1, 2)) + result = [None, None] + pm.eval("""(result, myit) => {let index = 0; for (const value of myit) {result[index++] = value}}""")(result, myit) + assert result[0] == 1.0 + assert result[1] == 2.0 diff --git a/tests/python/test_bigints.py b/tests/python/test_bigints.py index f5b97c16..69c8c31e 100644 --- a/tests/python/test_bigints.py +++ b/tests/python/test_bigints.py @@ -2,138 +2,151 @@ import pythonmonkey as pm import random + def test_eval_numbers_bigints(): - def test_bigint(py_number: int): - js_number = pm.eval(f'{repr(py_number)}n') - assert py_number == js_number - - test_bigint(0) - test_bigint(1) - test_bigint(-1) - - # CPython would reuse the objects for small ints in range [-5, 256] - # Making sure we don't do any changes on them - def test_cached_int_object(py_number): - # type is still int - assert type(py_number) == int - assert type(py_number) != pm.bigint - test_bigint(py_number) - assert type(py_number) == int - assert type(py_number) != pm.bigint - # the value doesn't change - # TODO (Tom Tang): Find a way to create a NEW int object with the same value, because int literals also reuse the cached int objects - for _ in range(2): - test_cached_int_object(0) # _PyLong_FromByteArray reuses the int 0 object, - # see https://github.com/python/cpython/blob/3.9/Objects/longobject.c#L862 - for i in range(10): - test_cached_int_object(random.randint(-5, 256)) - - test_bigint(18014398509481984) # 2**54 - test_bigint(-18014398509481984) # -2**54 - test_bigint(18446744073709551615) # 2**64-1 - test_bigint(18446744073709551616) # 2**64 - test_bigint(-18446744073709551617) # -2**64-1 - - limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 - # = 2**300 + def test_bigint(py_number: int): + js_number = pm.eval(f'{repr(py_number)}n') + assert py_number == js_number + + test_bigint(0) + test_bigint(1) + test_bigint(-1) + + # CPython would reuse the objects for small ints in range [-5, 256] + # Making sure we don't do any changes on them + def test_cached_int_object(py_number): + + # type is still int + assert type(py_number) is int + assert not isinstance(py_number, pm.bigint) + test_bigint(py_number) + assert type(py_number) is int + assert not isinstance(py_number, pm.bigint) + # the value doesn't change + # TODO (Tom Tang): Find a way to create a NEW int object with the same + # value, because int literals also reuse the cached int objects + for _ in range(2): + test_cached_int_object(0) # _PyLong_FromByteArray reuses the int 0 object, + # see https://github.com/python/cpython/blob/3.9/Objects/longobject.c#L862 for i in range(10): - py_number = random.randint(-limit, limit) - test_bigint(py_number) + test_cached_int_object(random.randint(-5, 256)) + + test_bigint(18014398509481984) # 2**54 + test_bigint(-18014398509481984) # -2**54 + test_bigint(18446744073709551615) # 2**64-1 + test_bigint(18446744073709551616) # 2**64 + test_bigint(-18446744073709551617) # -2**64-1 + + limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 + # = 2**300 + for i in range(10): + py_number = random.randint(-limit, limit) + test_bigint(py_number) + + # TODO (Tom Tang): test -0 (negative zero) + # There's no -0 in both Python int and JS BigInt, + # but this could be possible in JS BigInt's internal representation as it uses a sign bit flag. + # On the other hand, Python int uses `ob_size` 0 for 0, >0 for positive values, <0 for negative values - # TODO (Tom Tang): test -0 (negative zero) - # There's no -0 in both Python int and JS BigInt, - # but this could be possible in JS BigInt's internal representation as it uses a sign bit flag. - # On the other hand, Python int uses `ob_size` 0 for 0, >0 for positive values, <0 for negative values def test_eval_boxed_numbers_bigints(): - def test_boxed_bigint(py_number: int): - # `BigInt()` can only be called without `new` - # https://tc39.es/ecma262/#sec-bigint-constructor - js_number = pm.eval(f'new Object({repr(py_number)}n)') - assert py_number == js_number - - test_boxed_bigint(0) - test_boxed_bigint(1) - test_boxed_bigint(-1) - - limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 - # = 2**300 - for i in range(10): - py_number = random.randint(-limit, limit) - test_boxed_bigint(py_number) + def test_boxed_bigint(py_number: int): + # `BigInt()` can only be called without `new` + # https://tc39.es/ecma262/#sec-bigint-constructor + js_number = pm.eval(f'new Object({repr(py_number)}n)') + assert py_number == js_number + + test_boxed_bigint(0) + test_boxed_bigint(1) + test_boxed_bigint(-1) + + limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 + # = 2**300 + for i in range(10): + py_number = random.randint(-limit, limit) + test_boxed_bigint(py_number) + def test_eval_functions_bigints(): - ident = pm.eval("(a) => { return a }") - add = pm.eval("(a, b) => { return a + b }") - - int1 = random.randint(-1000000,1000000) - bigint1 = pm.bigint(int1) - assert int1 == bigint1 - - # should return pm.bigint - assert type(ident(bigint1)) == pm.bigint - assert ident(bigint1) is not bigint1 - # should return float (because JS number is float64) - assert type(ident(int1)) == float - assert ident(int1) == ident(bigint1) - - # should raise exception on ints > (2^53-1), or < -(2^53-1) - def not_raise(num): - ident(num) - def should_raise(num): - with pytest.raises(OverflowError, match="Use pythonmonkey.bigint instead"): - ident(num) - not_raise(9007199254740991) # 2**53-1, 0x433_FFFFFFFFFFFFF in float64 - should_raise(9007199254740992) # 2**53, 0x434_0000000000000 in float64 - should_raise(9007199254740993) # 2**53+1, NOT 0x434_0000000000001 (2**53+2) - not_raise(-9007199254740991) # -(2**53-1) - should_raise(-9007199254740992) # -(2**53) - should_raise(-9007199254740993) # -(2**53+1) - - # should also raise exception on large integers (>=2**53) that can be exactly represented by a float64 - # in our current implementation - should_raise(9007199254740994) # 2**53+2, 0x434_0000000000001 in float64 - should_raise(2**61+2**9) # 0x43C_0000000000001 in float64 - - # should raise "Use pythonmonkey.bigint" instead of `PyLong_AsLongLong`'s "OverflowError: int too big to convert" on ints larger than 64bits - should_raise(2**65) - should_raise(-2**65) - not_raise(pm.bigint(2**65)) - not_raise(pm.bigint(-2**65)) - - # should raise JS error when mixing a BigInt with a number in arithmetic operations - def should_js_error(a, b): - with pytest.raises(pm.SpiderMonkeyError, match="can't convert BigInt to number"): - add(a, b) - should_js_error(pm.bigint(0), 0) - should_js_error(pm.bigint(1), 2) - should_js_error(3, pm.bigint(4)) - should_js_error(-5, pm.bigint(6)) - - assert add(pm.bigint(0), pm.bigint(0)) == 0 - assert add(pm.bigint(1), pm.bigint(0)) == 1 - assert add(pm.bigint(1), pm.bigint(2)) == 3 - assert add(pm.bigint(-1), pm.bigint(1)) == 0 - assert add(pm.bigint(2**60), pm.bigint(0)) == 1152921504606846976 - assert add(pm.bigint(2**65), pm.bigint(-2**65-1)) == -1 - - # fuzztest - limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 # 2**300 - for i in range(10): - num1 = random.randint(-limit, limit) - num2 = random.randint(-limit, limit) - assert add(pm.bigint(num1), pm.bigint(num2)) == num1+num2 + ident = pm.eval("(a) => { return a }") + add = pm.eval("(a, b) => { return a + b }") + + int1 = random.randint(-1000000, 1000000) + bigint1 = pm.bigint(int1) + assert int1 == bigint1 + + # should return pm.bigint + assert type(ident(bigint1)) == pm.bigint + assert ident(bigint1) is not bigint1 + # should return float (because JS number is float64) + assert type(ident(int1)) == float + assert ident(int1) == ident(bigint1) + + # should raise exception on ints > (2^53-1), or < -(2^53-1) + def not_raise(num): + ident(num) + + def should_raise(num): + with pytest.raises(OverflowError, match="Use pythonmonkey.bigint instead"): + ident(num) + # autopep8: off + not_raise(9007199254740991) # +(2**53-1), 0x433_FFFFFFFFFFFFF in float64 + should_raise(9007199254740992) # +(2**53 ), 0x434_0000000000000 in float64 + should_raise(9007199254740993) # +(2**53+1), NOT 0x434_0000000000001 (2**53+2) + not_raise(-9007199254740991) # -(2**53-1) + should_raise(-9007199254740992) # -(2**53 ) + should_raise(-9007199254740993) # -(2**53+1) + # autopep8: on + + # should also raise exception on large integers (>=2**53) that can be exactly represented by a float64 + # in our current implementation + # autopep8: off + should_raise(9007199254740994) # 2**53+2, 0x434_0000000000001 in float64 + should_raise(2**61 + 2**9) # 0x43C_0000000000001 in float64 + # autopep8: on + + # should raise "Use pythonmonkey.bigint" instead of `PyLong_AsLongLong`'s + # "OverflowError: int too big to convert" on ints larger than 64bits + should_raise(2**65) + should_raise(-2**65) + not_raise(pm.bigint(2**65)) + not_raise(pm.bigint(-2**65)) + + # should raise JS error when mixing a BigInt with a number in arithmetic operations + def should_js_error(a, b): + with pytest.raises(pm.SpiderMonkeyError, match="can't convert BigInt to number"): + add(a, b) + should_js_error(pm.bigint(0), 0) + should_js_error(pm.bigint(1), 2) + should_js_error(3, pm.bigint(4)) + should_js_error(-5, pm.bigint(6)) + + assert add(pm.bigint(0), pm.bigint(0)) == 0 + assert add(pm.bigint(1), pm.bigint(0)) == 1 + assert add(pm.bigint(1), pm.bigint(2)) == 3 + assert add(pm.bigint(-1), pm.bigint(1)) == 0 + assert add(pm.bigint(2**60), pm.bigint(0)) == 1152921504606846976 + assert add(pm.bigint(2**65), pm.bigint(-2**65 - 1)) == -1 + + # fuzztest + limit = 2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397376 # 2**300 + for i in range(10): + num1 = random.randint(-limit, limit) + num2 = random.randint(-limit, limit) + assert add(pm.bigint(num1), pm.bigint(num2)) == num1 + num2 + def test_eval_functions_bigint_factorial(): - factorial = pm.eval("(num) => {let r = 1n; for(let i = 0n; i Number.MAX_SAFE_INTEGER - assert factorial(pm.bigint(21)) == 51090942171709440000 # > 64 bit int - assert factorial(pm.bigint(35)) == 10333147966386144929666651337523200000000 # > 128 bit + factorial = pm.eval("(num) => {let r = 1n; for(let i = 0n; i Number.MAX_SAFE_INTEGER + assert factorial(pm.bigint(21)) == 51090942171709440000 # > 64 bit int + assert factorial(pm.bigint(35)) == 10333147966386144929666651337523200000000 # > 128 bit + def test_eval_functions_bigint_crc32(): - crc_table_at = pm.eval(""" + crc_table_at = pm.eval(""" // translated from https://rosettacode.org/wiki/CRC-32#Python const crc_table = (function create_table() { const a = [] @@ -150,7 +163,7 @@ def test_eval_functions_bigint_crc32(): })(); (n) => crc_table[n] """) - assert type(crc_table_at(1)) == pm.bigint - assert crc_table_at(0) == 0 - assert crc_table_at(1) == 1996959894 - assert crc_table_at(255) == 755167117 # last item + assert type(crc_table_at(1)) == pm.bigint + assert crc_table_at(0) == 0 + assert crc_table_at(1) == 1996959894 + assert crc_table_at(255) == 755167117 # last item diff --git a/tests/python/test_buffer_typed_array.py b/tests/python/test_buffer_typed_array.py index 04171848..dabc8c61 100644 --- a/tests/python/test_buffer_typed_array.py +++ b/tests/python/test_buffer_typed_array.py @@ -1,198 +1,211 @@ import pytest import pythonmonkey as pm import gc -import numpy, array, struct +import numpy +import array +import struct + def test_py_buffer_to_js_typed_array(): - # JS TypedArray/ArrayBuffer should coerce to Python memoryview type - def assert_js_to_py_memoryview(buf: memoryview): - assert type(buf) is memoryview - assert None == buf.obj # https://docs.python.org/3.9/c-api/buffer.html#c.Py_buffer.obj - assert 2 * 4 == buf.nbytes # 2 elements * sizeof(int32_t) - assert "02000000ffffffff" == buf.hex() # native (little) endian - buf1 = pm.eval("new Int32Array([2,-1])") - buf2 = pm.eval("new Int32Array([2,-1]).buffer") - assert_js_to_py_memoryview(buf1) - assert_js_to_py_memoryview(buf2) - assert [2, -1] == buf1.tolist() - assert [2, 0, 0, 0, 255, 255, 255, 255] == buf2.tolist() - assert -1 == buf1[1] - assert 255 == buf2[7] - with pytest.raises(IndexError, match="index out of bounds on dimension 1"): - buf1[2] - with pytest.raises(IndexError, match="index out of bounds on dimension 1"): - buf2[8] - del buf1, buf2 - - # test element value ranges - buf3 = pm.eval("new Uint8Array(1)") - with pytest.raises(ValueError, match="memoryview: invalid value for format 'B'"): - buf3[0] = 256 - with pytest.raises(ValueError, match="memoryview: invalid value for format 'B'"): - buf3[0] = -1 - with pytest.raises(IndexError, match="index out of bounds on dimension 1"): # no automatic resize - buf3[1] = 0 - del buf3 - - # Python buffers should coerce to JS TypedArray - # and the typecode maps to TypedArray subtype (Uint8Array, Float64Array, ...) - assert True == pm.eval("(arr)=>arr instanceof Uint8Array")( bytearray([1,2,3]) ) - assert True == pm.eval("(arr)=>arr instanceof Uint8Array")( numpy.array([1], dtype=numpy.uint8) ) - assert True == pm.eval("(arr)=>arr instanceof Uint16Array")( numpy.array([1], dtype=numpy.uint16) ) - assert True == pm.eval("(arr)=>arr instanceof Uint32Array")( numpy.array([1], dtype=numpy.uint32) ) - assert True == pm.eval("(arr)=>arr instanceof BigUint64Array")( numpy.array([1], dtype=numpy.uint64) ) - assert True == pm.eval("(arr)=>arr instanceof Int8Array")( numpy.array([1], dtype=numpy.int8) ) - assert True == pm.eval("(arr)=>arr instanceof Int16Array")( numpy.array([1], dtype=numpy.int16) ) - assert True == pm.eval("(arr)=>arr instanceof Int32Array")( numpy.array([1], dtype=numpy.int32) ) - assert True == pm.eval("(arr)=>arr instanceof BigInt64Array")( numpy.array([1], dtype=numpy.int64) ) - assert True == pm.eval("(arr)=>arr instanceof Float32Array")( numpy.array([1], dtype=numpy.float32) ) - assert True == pm.eval("(arr)=>arr instanceof Float64Array")( numpy.array([1], dtype=numpy.float64) ) - assert pm.eval("new Uint8Array([1])").format == "B" - assert pm.eval("new Uint16Array([1])").format == "H" - assert pm.eval("new Uint32Array([1])").format == "I" # FIXME (Tom Tang): this is "L" on 32-bit systems - assert pm.eval("new BigUint64Array([1n])").format == "Q" - assert pm.eval("new Int8Array([1])").format == "b" - assert pm.eval("new Int16Array([1])").format == "h" - assert pm.eval("new Int32Array([1])").format == "i" - assert pm.eval("new BigInt64Array([1n])").format == "q" - assert pm.eval("new Float32Array([1])").format == "f" - assert pm.eval("new Float64Array([1])").format == "d" - - # not enough bytes to populate an element of the TypedArray - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: buffer length for BigInt64Array should be a multiple of 8"): - pm.eval("(arr) => new BigInt64Array(arr.buffer)")(array.array('i', [-11111111])) - - # TypedArray with `byteOffset` and `length` - arr1 = array.array('i', [-11111111, 22222222, -33333333, 44444444]) - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: invalid or out-of-range index"): - pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ -4)")(arr1) - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: start offset of Int32Array should be a multiple of 4"): - pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 1)")(arr1) - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: size of buffer is too small for Int32Array with byteOffset"): - pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 20)")(arr1) - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: invalid or out-of-range index"): - pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 4, /*length*/ -1)")(arr1) - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: attempting to construct out-of-bounds Int32Array on ArrayBuffer"): - pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 4, /*length*/ 4)")(arr1) - arr2 = pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 4, /*length*/ 2)")(arr1) - assert 2 * 4 == arr2.nbytes # 2 elements * sizeof(int32_t) - assert [22222222, -33333333] == arr2.tolist() - assert "8e155301ab5f03fe" == arr2.hex() # native (little) endian - assert 22222222 == arr2[0] # offset 1 int32 - with pytest.raises(IndexError, match="index out of bounds on dimension 1"): - arr2[2] - arr3 = pm.eval("(arr) => new Int32Array(arr.buffer, 16 /* byteOffset */)")(arr1) # empty Int32Array - assert 0 == arr3.nbytes - del arr3 - - # test GC - del arr1 - gc.collect(), pm.collect() - gc.collect(), pm.collect() - # TODO (Tom Tang): the 0th element in the underlying buffer is still accessible after GC, even is not referenced by the JS TypedArray with byteOffset - del arr2 - - # mutation - mut_arr_original = bytearray(4) - pm.eval(""" + # JS TypedArray/ArrayBuffer should coerce to Python memoryview type + def assert_js_to_py_memoryview(buf: memoryview): + assert type(buf) is memoryview + assert None is buf.obj # https://docs.python.org/3.9/c-api/buffer.html#c.Py_buffer.obj + assert 2 * 4 == buf.nbytes # 2 elements * sizeof(int32_t) + assert "02000000ffffffff" == buf.hex() # native (little) endian + buf1 = pm.eval("new Int32Array([2,-1])") + buf2 = pm.eval("new Int32Array([2,-1]).buffer") + assert_js_to_py_memoryview(buf1) + assert_js_to_py_memoryview(buf2) + assert [2, -1] == buf1.tolist() + assert [2, 0, 0, 0, 255, 255, 255, 255] == buf2.tolist() + assert -1 == buf1[1] + assert 255 == buf2[7] + with pytest.raises(IndexError, match="index out of bounds on dimension 1"): + buf1[2] + with pytest.raises(IndexError, match="index out of bounds on dimension 1"): + buf2[8] + del buf1, buf2 + + # test element value ranges + buf3 = pm.eval("new Uint8Array(1)") + with pytest.raises(ValueError, match="memoryview: invalid value for format 'B'"): + buf3[0] = 256 + with pytest.raises(ValueError, match="memoryview: invalid value for format 'B'"): + buf3[0] = -1 + with pytest.raises(IndexError, match="index out of bounds on dimension 1"): # no automatic resize + buf3[1] = 0 + del buf3 + + # Python buffers should coerce to JS TypedArray + # and the typecode maps to TypedArray subtype (Uint8Array, Float64Array, ...) + assert pm.eval("(arr)=>arr instanceof Uint8Array")(bytearray([1, 2, 3])) + assert pm.eval("(arr)=>arr instanceof Uint8Array")(numpy.array([1], dtype=numpy.uint8)) + assert pm.eval("(arr)=>arr instanceof Uint16Array")(numpy.array([1], dtype=numpy.uint16)) + assert pm.eval("(arr)=>arr instanceof Uint32Array")(numpy.array([1], dtype=numpy.uint32)) + assert pm.eval("(arr)=>arr instanceof BigUint64Array")(numpy.array([1], dtype=numpy.uint64)) + assert pm.eval("(arr)=>arr instanceof Int8Array")(numpy.array([1], dtype=numpy.int8)) + assert pm.eval("(arr)=>arr instanceof Int16Array")(numpy.array([1], dtype=numpy.int16)) + assert pm.eval("(arr)=>arr instanceof Int32Array")(numpy.array([1], dtype=numpy.int32)) + assert pm.eval("(arr)=>arr instanceof BigInt64Array")(numpy.array([1], dtype=numpy.int64)) + assert pm.eval("(arr)=>arr instanceof Float32Array")(numpy.array([1], dtype=numpy.float32)) + assert pm.eval("(arr)=>arr instanceof Float64Array")(numpy.array([1], dtype=numpy.float64)) + assert pm.eval("new Uint8Array([1])").format == "B" + assert pm.eval("new Uint16Array([1])").format == "H" + assert pm.eval("new Uint32Array([1])").format == "I" # FIXME (Tom Tang): this is "L" on 32-bit systems + assert pm.eval("new BigUint64Array([1n])").format == "Q" + assert pm.eval("new Int8Array([1])").format == "b" + assert pm.eval("new Int16Array([1])").format == "h" + assert pm.eval("new Int32Array([1])").format == "i" + assert pm.eval("new BigInt64Array([1n])").format == "q" + assert pm.eval("new Float32Array([1])").format == "f" + assert pm.eval("new Float64Array([1])").format == "d" + + # not enough bytes to populate an element of the TypedArray + with pytest.raises(pm.SpiderMonkeyError, + match="RangeError: buffer length for BigInt64Array should be a multiple of 8"): + pm.eval("(arr) => new BigInt64Array(arr.buffer)")(array.array('i', [-11111111])) + + # TypedArray with `byteOffset` and `length` + arr1 = array.array('i', [-11111111, 22222222, -33333333, 44444444]) + with pytest.raises(pm.SpiderMonkeyError, match="RangeError: invalid or out-of-range index"): + pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ -4)")(arr1) + with pytest.raises(pm.SpiderMonkeyError, match="RangeError: start offset of Int32Array should be a multiple of 4"): + pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 1)")(arr1) + with pytest.raises(pm.SpiderMonkeyError, + match="RangeError: size of buffer is too small for Int32Array with byteOffset"): + pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 20)")(arr1) + with pytest.raises(pm.SpiderMonkeyError, match="RangeError: invalid or out-of-range index"): + pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 4, /*length*/ -1)")(arr1) + with pytest.raises(pm.SpiderMonkeyError, + match="RangeError: attempting to construct out-of-bounds Int32Array on ArrayBuffer"): + pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 4, /*length*/ 4)")(arr1) + arr2 = pm.eval("(arr) => new Int32Array(arr.buffer, /*byteOffset*/ 4, /*length*/ 2)")(arr1) + assert 2 * 4 == arr2.nbytes # 2 elements * sizeof(int32_t) + assert [22222222, -33333333] == arr2.tolist() + assert "8e155301ab5f03fe" == arr2.hex() # native (little) endian + assert 22222222 == arr2[0] # offset 1 int32 + with pytest.raises(IndexError, match="index out of bounds on dimension 1"): + arr2[2] + arr3 = pm.eval("(arr) => new Int32Array(arr.buffer, 16 /* byteOffset */)")(arr1) # empty Int32Array + assert 0 == arr3.nbytes + del arr3 + + # test GC + del arr1 + gc.collect(), pm.collect() + gc.collect(), pm.collect() + # TODO (Tom Tang): the 0th element in the underlying buffer is still + # accessible after GC, even is not referenced by the JS TypedArray with + # byteOffset + del arr2 + + # mutation + mut_arr_original = bytearray(4) + pm.eval(""" (/* @type Uint8Array */ arr) => { // 2.25 in float32 little endian arr[2] = 0x10 arr[3] = 0x40 } """)(mut_arr_original) - assert 0x10 == mut_arr_original[2] - assert 0x40 == mut_arr_original[3] - # mutation to a different TypedArray accessing the same underlying data block will also change the original buffer - def do_mutation(mut_arr_js): - assert 2.25 == mut_arr_js[0] - mut_arr_js[0] = 225.50048828125 # float32 little endian: 0x 20 80 61 43 - assert "20806143" == mut_arr_original.hex() - assert 225.50048828125 == array.array("f", mut_arr_original)[0] - mut_arr_new = pm.eval(""" + assert 0x10 == mut_arr_original[2] + assert 0x40 == mut_arr_original[3] + # mutation to a different TypedArray accessing the same underlying data block will also change the original buffer + + def do_mutation(mut_arr_js): + assert 2.25 == mut_arr_js[0] + mut_arr_js[0] = 225.50048828125 # float32 little endian: 0x 20 80 61 43 + assert "20806143" == mut_arr_original.hex() + assert 225.50048828125 == array.array("f", mut_arr_original)[0] + mut_arr_new = pm.eval(""" (/* @type Uint8Array */ arr, do_mutation) => { const mut_arr_js = new Float32Array(arr.buffer) do_mutation(mut_arr_js) return arr } """)(mut_arr_original, do_mutation) - assert [0x20, 0x80, 0x61, 0x43] == mut_arr_new.tolist() - - # simple 1-D numpy array should just work as well - numpy_int16_array = numpy.array([0, 1, 2, 3], dtype=numpy.int16) - assert "0,1,2,3" == pm.eval("(typedArray) => typedArray.toString()")(numpy_int16_array) - assert 3.0 == pm.eval("(typedArray) => typedArray[3]")(numpy_int16_array) - assert True == pm.eval("(typedArray) => typedArray instanceof Int16Array")(numpy_int16_array) - numpy_memoryview = pm.eval("(typedArray) => typedArray")(numpy_int16_array) - assert 2 == numpy_memoryview[2] - assert 4 * 2 == numpy_memoryview.nbytes # 4 elements * sizeof(int16_t) - assert "h" == numpy_memoryview.format # the type code for int16 is 'h', see https://docs.python.org/3.9/library/array.html - with pytest.raises(IndexError, match="index out of bounds on dimension 1"): - numpy_memoryview[4] - - # can work for empty Python buffer - def assert_empty_py_buffer(buf, type: str): - assert 0 == pm.eval("(typedArray) => typedArray.length")(buf) - assert None == pm.eval("(typedArray) => typedArray[0]")(buf) # `undefined` - assert True == pm.eval("(typedArray) => typedArray instanceof "+type)(buf) - assert_empty_py_buffer(bytearray(b''), "Uint8Array") - assert_empty_py_buffer(numpy.array([], dtype=numpy.uint64), "BigUint64Array") - assert_empty_py_buffer(array.array('d', []), "Float64Array") - - # can work for empty TypedArray - def assert_empty_typedarray(buf: memoryview, typecode: str): - assert typecode == buf.format - assert struct.calcsize(typecode) == buf.itemsize - assert 0 == buf.nbytes - assert "" == buf.hex() - assert b"" == buf.tobytes() - assert [] == buf.tolist() - buf.release() - assert_empty_typedarray(pm.eval("new BigInt64Array()"), "q") - assert_empty_typedarray(pm.eval("new Float32Array(new ArrayBuffer(4), 4 /*byteOffset*/)"), "f") - assert_empty_typedarray(pm.eval("(arr)=>arr")( bytearray([]) ), "B") - assert_empty_typedarray(pm.eval("(arr)=>arr")( numpy.array([], dtype=numpy.uint16) ),"H") - assert_empty_typedarray(pm.eval("(arr)=>arr")( array.array("d", []) ),"d") - - # can work for empty ArrayBuffer - def assert_empty_arraybuffer(buf): - assert "B" == buf.format - assert 1 == buf.itemsize - assert 0 == buf.nbytes - assert "" == buf.hex() - assert b"" == buf.tobytes() - assert [] == buf.tolist() - buf.release() - assert_empty_arraybuffer(pm.eval("new ArrayBuffer()")) - assert_empty_arraybuffer(pm.eval("new Uint8Array().buffer")) - assert_empty_arraybuffer(pm.eval("new Float64Array().buffer")) - assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")( bytearray([]) )) - assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")( pm.eval("(arr)=>arr.buffer")(bytearray()) )) - assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")( numpy.array([], dtype=numpy.uint64) )) - assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")( array.array("d", []) )) - - # TODO (Tom Tang): shared ArrayBuffer should be disallowed - # pm.eval("new WebAssembly.Memory({ initial: 1, maximum: 1, shared: true }).buffer") - - # TODO (Tom Tang): once a JS ArrayBuffer is transferred to a worker thread, it should be invalidated in Python-land as well - - # TODO (Tom Tang): error for detached ArrayBuffer, or should it be considered as empty? - - # should error on immutable Python buffers - # Note: Python `bytes` type must be converted to a (mutable) `bytearray` because there's no such a concept of read-only ArrayBuffer in JS - with pytest.raises(BufferError, match="Object is not writable."): - pm.eval("(typedArray) => {}")(b'') - immutable_numpy_array = numpy.arange(10) - immutable_numpy_array.setflags(write=False) - with pytest.raises(ValueError, match="buffer source array is read-only"): - pm.eval("(typedArray) => {}")(immutable_numpy_array) - - # buffer should be in C order (row major) - fortran_order_arr = numpy.array([[1, 2], [3, 4]], order="F") # 1-D array is always considered C-contiguous because it doesn't matter if it's row or column major in 1-D - with pytest.raises(ValueError, match="ndarray is not C-contiguous"): - pm.eval("(typedArray) => {}")(fortran_order_arr) - - # disallow multidimensional array - numpy_2d_array = numpy.array([[1, 2], [3, 4]], order="C") - with pytest.raises(BufferError, match="multidimensional arrays are not allowed"): - pm.eval("(typedArray) => {}")(numpy_2d_array) + assert [0x20, 0x80, 0x61, 0x43] == mut_arr_new.tolist() + + # simple 1-D numpy array should just work as well + numpy_int16_array = numpy.array([0, 1, 2, 3], dtype=numpy.int16) + assert "0,1,2,3" == pm.eval("(typedArray) => typedArray.toString()")(numpy_int16_array) + assert 3.0 == pm.eval("(typedArray) => typedArray[3]")(numpy_int16_array) + assert pm.eval("(typedArray) => typedArray instanceof Int16Array")(numpy_int16_array) + numpy_memoryview = pm.eval("(typedArray) => typedArray")(numpy_int16_array) + assert 2 == numpy_memoryview[2] + assert 4 * 2 == numpy_memoryview.nbytes # 4 elements * sizeof(int16_t) + # the type code for int16 is 'h', see https://docs.python.org/3.9/library/array.html + assert "h" == numpy_memoryview.format + with pytest.raises(IndexError, match="index out of bounds on dimension 1"): + numpy_memoryview[4] + + # can work for empty Python buffer + def assert_empty_py_buffer(buf, type: str): + assert 0 == pm.eval("(typedArray) => typedArray.length")(buf) + assert None is pm.eval("(typedArray) => typedArray[0]")(buf) # `undefined` + assert pm.eval("(typedArray) => typedArray instanceof " + type)(buf) + assert_empty_py_buffer(bytearray(b''), "Uint8Array") + assert_empty_py_buffer(numpy.array([], dtype=numpy.uint64), "BigUint64Array") + assert_empty_py_buffer(array.array('d', []), "Float64Array") + + # can work for empty TypedArray + def assert_empty_typedarray(buf: memoryview, typecode: str): + assert typecode == buf.format + assert struct.calcsize(typecode) == buf.itemsize + assert 0 == buf.nbytes + assert "" == buf.hex() + assert b"" == buf.tobytes() + assert [] == buf.tolist() + buf.release() + assert_empty_typedarray(pm.eval("new BigInt64Array()"), "q") + assert_empty_typedarray(pm.eval("new Float32Array(new ArrayBuffer(4), 4 /*byteOffset*/)"), "f") + assert_empty_typedarray(pm.eval("(arr)=>arr")(bytearray([])), "B") + assert_empty_typedarray(pm.eval("(arr)=>arr")(numpy.array([], dtype=numpy.uint16)), "H") + assert_empty_typedarray(pm.eval("(arr)=>arr")(array.array("d", [])), "d") + + # can work for empty ArrayBuffer + def assert_empty_arraybuffer(buf): + assert "B" == buf.format + assert 1 == buf.itemsize + assert 0 == buf.nbytes + assert "" == buf.hex() + assert b"" == buf.tobytes() + assert [] == buf.tolist() + buf.release() + assert_empty_arraybuffer(pm.eval("new ArrayBuffer()")) + assert_empty_arraybuffer(pm.eval("new Uint8Array().buffer")) + assert_empty_arraybuffer(pm.eval("new Float64Array().buffer")) + assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")(bytearray([]))) + assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")(pm.eval("(arr)=>arr.buffer")(bytearray()))) + assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")(numpy.array([], dtype=numpy.uint64))) + assert_empty_arraybuffer(pm.eval("(arr)=>arr.buffer")(array.array("d", []))) + + # TODO (Tom Tang): shared ArrayBuffer should be disallowed + # pm.eval("new WebAssembly.Memory({ initial: 1, maximum: 1, shared: true }).buffer") + + # TODO (Tom Tang): once a JS ArrayBuffer is transferred to a worker + # thread, it should be invalidated in Python-land as well + + # TODO (Tom Tang): error for detached ArrayBuffer, or should it be considered as empty? + + # should error on immutable Python buffers + # Note: Python `bytes` type must be converted to a (mutable) `bytearray` + # because there's no such a concept of read-only ArrayBuffer in JS + with pytest.raises(BufferError, match="Object is not writable."): + pm.eval("(typedArray) => {}")(b'') + immutable_numpy_array = numpy.arange(10) + immutable_numpy_array.setflags(write=False) + with pytest.raises(ValueError, match="buffer source array is read-only"): + pm.eval("(typedArray) => {}")(immutable_numpy_array) + + # buffer should be in C order (row major) + # 1-D array is always considered C-contiguous because it doesn't matter if it's row or column major in 1-D + fortran_order_arr = numpy.array([[1, 2], [3, 4]], order="F") + with pytest.raises(ValueError, match="ndarray is not C-contiguous"): + pm.eval("(typedArray) => {}")(fortran_order_arr) + + # disallow multidimensional array + numpy_2d_array = numpy.array([[1, 2], [3, 4]], order="C") + with pytest.raises(BufferError, match="multidimensional arrays are not allowed"): + pm.eval("(typedArray) => {}")(numpy_2d_array) diff --git a/tests/python/test_dict_methods.py b/tests/python/test_dict_methods.py index e572e129..a303f73a 100644 --- a/tests/python/test_dict_methods.py +++ b/tests/python/test_dict_methods.py @@ -1,478 +1,557 @@ import pythonmonkey as pm # get + + def test_get_no_default(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.get('fruit') - assert foundKey == 'apple' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.get('fruit') + assert foundKey == 'apple' + def test_get_no_default_not_found(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.get('fuit') - assert foundKey == None + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.get('fuit') + assert foundKey is None + def test_get_default_not_found(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.get('fuit', 'orange') - assert foundKey == 'orange' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.get('fuit', 'orange') + assert foundKey == 'orange' + def test_get_no_params(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - try: - likes.get() - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "get expected at least 1 argument, got 0" + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + try: + likes.get() + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "get expected at least 1 argument, got 0" # setdefault + + def test_setdefault_found(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.setdefault('color') - assert foundKey == 'blue' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.setdefault('color') + assert foundKey == 'blue' + def test_setdefault_found_ignore_default(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.setdefault('color', 'yello') - assert foundKey == 'blue' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.setdefault('color', 'yello') + assert foundKey == 'blue' + def test_setdefault_not_found_no_default(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.setdefault('colo') - assert likes['colo'] == None - assert foundKey == None + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.setdefault('colo') + assert likes['colo'] is None + assert foundKey is None + def test_setdefault_not_found_with_default(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.setdefault('colo', 'yello') - assert likes['colo'] == 'yello' - assert foundKey == 'yello' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.setdefault('colo', 'yello') + assert likes['colo'] == 'yello' + assert foundKey == 'yello' + def test_setdefault_no_params(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - try: - likes.setdefault() - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "setdefault expected at least 1 argument, got 0" + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + try: + likes.setdefault() + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "setdefault expected at least 1 argument, got 0" + def test_setdefault_with_shadowing(): - jsObj = pm.eval("({get: 'value'})") - a = jsObj.setdefault("get", "val") - assert a == 'value' + jsObj = pm.eval("({get: 'value'})") + a = jsObj.setdefault("get", "val") + assert a == 'value' + +# pop + -#pop def test_pop_found(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.pop('color') - assert likes['color'] == None - assert foundKey == 'blue' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.pop('color') + assert likes['color'] is None + assert foundKey == 'blue' + def test_pop_not_found(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - try: - likes.pop('colo') - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "'colo'" + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + try: + likes.pop('colo') + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "'colo'" + def test_pop_twice_not_found(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + likes.pop('color') + try: likes.pop('color') - try: - likes.pop('color') - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "'color'" + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "'color'" + def test_pop_found_ignore_default(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.pop('color', 'u') - assert foundKey == 'blue' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.pop('color', 'u') + assert foundKey == 'blue' + def test_pop_not_found_with_default(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - foundKey = likes.pop('colo', 'unameit') - assert foundKey == 'unameit' + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + foundKey = likes.pop('colo', 'unameit') + assert foundKey == 'unameit' + def test_pop_not_found_no_default(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - try: - likes.pop('colo') - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "'colo'" + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + try: + likes.pop('colo') + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "'colo'" + def test_pop_no_params(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - try: - likes.pop() - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "pop expected at least 1 argument, got 0" - -#clear + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + try: + likes.pop() + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "pop expected at least 1 argument, got 0" + +# clear + + def test_clear(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - likes.clear() - assert len(likes) == 0 + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + likes.clear() + assert len(likes) == 0 + +# copy + -#copy def test_copy(): - likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') - otherLikes = likes.copy() - otherLikes["color"] = "yellow" + likes = pm.eval('({"color": "blue", "fruit": "apple", "pet": "dog"})') + otherLikes = likes.copy() + otherLikes["color"] = "yellow" + + assert likes == {"color": "blue", "fruit": "apple", "pet": "dog"} + assert otherLikes == {"color": "yellow", "fruit": "apple", "pet": "dog"} + +# update - assert likes == {"color": "blue", "fruit": "apple", "pet": "dog"} - assert otherLikes == {"color": "yellow", "fruit": "apple", "pet": "dog"} -#update def test_update_true_dict_right(): - a = pm.eval("({'c':5})") - b = {'d':6.0} - a.update(b) - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + a = pm.eval("({'c':5})") + b = {'d': 6.0} + a.update(b) + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_update_true_dict_left(): - a = {'d':6.0} - b = pm.eval("({'c':5})") - a.update(b) - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'c': 5.0} + a = {'d': 6.0} + b = pm.eval("({'c':5})") + a.update(b) + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'c': 5.0} + def test_update_true_two_pm_dicts(): - a = pm.eval("({'c':5})") - b = pm.eval("({'d':6})") - a.update(b) - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + a = pm.eval("({'c':5})") + b = pm.eval("({'d':6})") + a.update(b) + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_update_two_args(): - a = pm.eval("({'c':5})") - a.update(B='For', C='Geeks') - assert a == {'c': 5.0, 'B': 'For', 'C': 'Geeks'} + a = pm.eval("({'c':5})") + a.update(B='For', C='Geeks') + assert a == {'c': 5.0, 'B': 'For', 'C': 'Geeks'} + def test_update_iterable(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - car.update([('y', 3), ('z', 0)]) - assert car == {'brand': 'Ford', 'model': 'Mustang', 'year': 1964, 'y': 3, 'z': 0} - + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + car.update([('y', 3), ('z', 0)]) + assert car == {'brand': 'Ford', 'model': 'Mustang', 'year': 1964, 'y': 3, 'z': 0} + + def test_update_iterable_wrong_type(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = [1,2] - try: - car.update(a) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "cannot convert dictionary update sequence element #0 to a sequence" - -#keys + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = [1, 2] + try: + car.update(a) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "cannot convert dictionary update sequence element #0 to a sequence" + +# keys + + def test_keys_iter(): - obj = pm.eval("({ a: 123, b: 'test' })") - result = [] - for i in obj.keys(): - result.append(i) - assert result == ['a', 'b'] + obj = pm.eval("({ a: 123, b: 'test' })") + result = [] + for i in obj.keys(): + result.append(i) + assert result == ['a', 'b'] + def test_keys_iter_reverse(): - obj = pm.eval("({ a: 123, b: 'test' })") - result = [] - for i in reversed(obj.keys()): - result.append(i) - assert result == ['b', 'a'] + obj = pm.eval("({ a: 123, b: 'test' })") + result = [] + for i in reversed(obj.keys()): + result.append(i) + assert result == ['b', 'a'] + def test_keys_list(): - obj = pm.eval("({ a: 123, b: 'test' })") - assert list(obj.keys()) == ['a', 'b'] + obj = pm.eval("({ a: 123, b: 'test' })") + assert list(obj.keys()) == ['a', 'b'] + def test_keys_repr(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = car.keys() - assert str(a) == "dict_keys(['brand', 'model', 'year'])" + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = car.keys() + assert str(a) == "dict_keys(['brand', 'model', 'year'])" + def test_keys_substract(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = car.keys() - b = a - ['brand'] - assert b == {'model', 'year'} + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = car.keys() + b = a - ['brand'] + assert b == {'model', 'year'} + def test_keys_richcompare_two_own(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = car.keys() - b = car.keys() - assert a == b - assert a is not b - assert a <= b - assert not(a < b) - assert a >= b - assert not(a > b) + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = car.keys() + b = car.keys() + assert a == b + assert a is not b + assert a <= b + assert not (a < b) + assert a >= b + assert not (a > b) + def test_keys_richcompare_one_own(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = car.keys() - care = {"brand": "Ford","model": "Mustang","year": 1964} - b = care.keys() - assert a == b - assert b == a - assert a <= b - assert not(a < b) - assert a >= b - assert not(a > b) - assert b <= a - assert not(b > a) - assert b >= a - assert not(b > a) + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = car.keys() + care = {"brand": "Ford", "model": "Mustang", "year": 1964} + b = care.keys() + assert a == b + assert b == a + assert a <= b + assert not (a < b) + assert a >= b + assert not (a > b) + assert b <= a + assert not (b > a) + assert b >= a + assert not (b > a) + def test_keys_intersect_one_own_smaller(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - b = keys & {'eggs', 'bacon', 'salad', 'jam'} - c = {'eggs', 'bacon', 'salad', 'jam'} & keys - assert b == {'bacon', 'eggs'} == c + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + b = keys & {'eggs', 'bacon', 'salad', 'jam'} + c = {'eggs', 'bacon', 'salad', 'jam'} & keys + assert b == {'bacon', 'eggs'} == c + def test_keys_intersect_two_own_smaller(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - dishes1 = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1})") - keys1 = dishes1.keys() - b = keys & keys1 - c = keys1 & keys - assert b == {'bacon', 'eggs', 'sausage'} == c + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + dishes1 = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1})") + keys1 = dishes1.keys() + b = keys & keys1 + c = keys1 & keys + assert b == {'bacon', 'eggs', 'sausage'} == c + def test_keys_intersect_one_own(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - dishes1 = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'peas':6} - keys1 = dishes1.keys() - b = keys & keys1 - c = keys1 & keys - assert b == {'bacon', 'eggs', 'sausage'} == c + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + dishes1 = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'peas': 6} + keys1 = dishes1.keys() + b = keys & keys1 + c = keys1 & keys + assert b == {'bacon', 'eggs', 'sausage'} == c + def test_keys_or(): - dishes =pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - others = keys | ['juice', 'juice', 'juice'] - assert others == {'bacon', 'spam', 'juice', 'sausage', 'eggs'} - others = ['apple'] | keys - assert others == {'bacon', 'spam', 'sausage', 'apple', 'eggs'} + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + others = keys | ['juice', 'juice', 'juice'] + assert others == {'bacon', 'spam', 'juice', 'sausage', 'eggs'} + others = ['apple'] | keys + assert others == {'bacon', 'spam', 'sausage', 'apple', 'eggs'} + def test_keys_xor(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - others = keys ^ {'sausage', 'juice'} - assert others == {'bacon', 'spam', 'juice', 'eggs'} + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + others = keys ^ {'sausage', 'juice'} + assert others == {'bacon', 'spam', 'juice', 'eggs'} + def test_keys_len(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - assert len(keys) == 4 + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + assert len(keys) == 4 + def test_keys_contains(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - assert 'eggs' in keys - assert 'egg' not in keys + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + assert 'eggs' in keys + assert 'egg' not in keys + def test_keys_isdisjoint_self(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - assert not keys.isdisjoint(keys) + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + assert not keys.isdisjoint(keys) + def test_keys_isdisjoint_true_keys(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - assert keys.isdisjoint({ "egg": 4, "e": 5, "f": 6}.keys()) + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + assert keys.isdisjoint({"egg": 4, "e": 5, "f": 6}.keys()) + def test_keys_isnotdisjoint_true_keys(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - assert not keys.isdisjoint({ "eggs": 4, "e": 5, "f": 6}.keys()) + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + assert not keys.isdisjoint({"eggs": 4, "e": 5, "f": 6}.keys()) + def test_keys_isnotdisjoint_own_keys(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - dishese = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys1 = dishese.keys() - assert not keys.isdisjoint(keys1) + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + dishese = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys1 = dishese.keys() + assert not keys.isdisjoint(keys1) + def test_keys_isnotdisjoint_own_keys_longer(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - dishese = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500, 'juice':6467})") - keys1 = dishese.keys() - assert not keys.isdisjoint(keys1) + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + dishese = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500, 'juice':6467})") + keys1 = dishese.keys() + assert not keys.isdisjoint(keys1) + def test_keys_isnotdisjoint_true_keys_longer(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - dishese = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500, 'juice':6467} - keys1 = dishese.keys() - assert not keys.isdisjoint(keys1) + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + dishese = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500, 'juice': 6467} + keys1 = dishese.keys() + assert not keys.isdisjoint(keys1) + def test_keys_update_object_updates_the_keys(): - employee = pm.eval("({'name': 'Phil', 'age': 22})") - dictionaryKeys = employee.keys() - employee.update({'salary': 3500.0}) - assert str(dictionaryKeys) == "dict_keys(['name', 'age', 'salary'])" + employee = pm.eval("({'name': 'Phil', 'age': 22})") + dictionaryKeys = employee.keys() + employee.update({'salary': 3500.0}) + assert str(dictionaryKeys) == "dict_keys(['name', 'age', 'salary'])" + def test_keys_mapping(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - keys = dishes.keys() - assert str(keys.mapping) == "{'eggs': 2.0, 'sausage': 1.0, 'bacon': 1.0, 'spam': 500.0}" - assert keys.mapping['spam'] == 500 + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + keys = dishes.keys() + assert str(keys.mapping) == "{'eggs': 2.0, 'sausage': 1.0, 'bacon': 1.0, 'spam': 500.0}" + assert keys.mapping['spam'] == 500 + +# values + -#values def test_values_iter(): - obj = pm.eval("({ a: 123, b: 'test' })") - result = [] - for i in obj.values(): - result.append(i) - assert result == [123, 'test'] + obj = pm.eval("({ a: 123, b: 'test' })") + result = [] + for i in obj.values(): + result.append(i) + assert result == [123, 'test'] + def test_values_iter_reversed(): - obj = pm.eval("({ a: 123, b: 'test' })") - result = [] - for i in reversed(obj.values()): - result.append(i) - assert result == ['test', 123] + obj = pm.eval("({ a: 123, b: 'test' })") + result = [] + for i in reversed(obj.values()): + result.append(i) + assert result == ['test', 123] + def test_values_list(): - obj = pm.eval("({ a: 123, b: 'test' })") - assert list(obj.values()) == [123, 'test'] + obj = pm.eval("({ a: 123, b: 'test' })") + assert list(obj.values()) == [123, 'test'] + def test_values_repr(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = car.values() - assert str(a) == "dict_values(['Ford', 'Mustang', 1964.0])" + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = car.values() + assert str(a) == "dict_values(['Ford', 'Mustang', 1964.0])" + def test_values_update_object_updates_the_values(): - employee = pm.eval("({'name': 'Phil', 'age': 22})") - dictionaryValues = employee.values() - employee.update({'salary': 3500.0}) - assert str(dictionaryValues) == "dict_values(['Phil', 22.0, 3500.0])" + employee = pm.eval("({'name': 'Phil', 'age': 22})") + dictionaryValues = employee.values() + employee.update({'salary': 3500.0}) + assert str(dictionaryValues) == "dict_values(['Phil', 22.0, 3500.0])" + def test_values_mapping(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - values = dishes.values() - assert str(values.mapping) == "{'eggs': 2.0, 'sausage': 1.0, 'bacon': 1.0, 'spam': 500.0}" - assert values.mapping['spam'] == 500 + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + values = dishes.values() + assert str(values.mapping) == "{'eggs': 2.0, 'sausage': 1.0, 'bacon': 1.0, 'spam': 500.0}" + assert values.mapping['spam'] == 500 + +# items + -#items def test_items_iter(): - obj = pm.eval("({ a: 123, b: 'test' })") - result = [] - for i in obj.items(): - result.append(i) - assert result == [('a', 123.0), ('b', 'test')] + obj = pm.eval("({ a: 123, b: 'test' })") + result = [] + for i in obj.items(): + result.append(i) + assert result == [('a', 123.0), ('b', 'test')] + def test_items_iter_reversed(): - obj = pm.eval("({ a: 123, b: 'test' })") - result = [] - for i in reversed(obj.items()): - result.append(i) - assert result == [ ('b', 'test'), ('a', 123.0)] - + obj = pm.eval("({ a: 123, b: 'test' })") + result = [] + for i in reversed(obj.items()): + result.append(i) + assert result == [('b', 'test'), ('a', 123.0)] + + def test_items_list(): - obj = pm.eval("({ a: 123, b: 'test' })") - assert list(obj.items()) == [('a', 123.0), ('b', 'test')] + obj = pm.eval("({ a: 123, b: 'test' })") + assert list(obj.items()) == [('a', 123.0), ('b', 'test')] + def test_items_repr(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = car.items() - assert str(a) == "dict_items([('brand', 'Ford'), ('model', 'Mustang'), ('year', 1964.0)])" + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = car.items() + assert str(a) == "dict_items([('brand', 'Ford'), ('model', 'Mustang'), ('year', 1964.0)])" + def test_items_substract(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = car.items() - b = a - [('brand', 'Ford')] - assert b == {('model', 'Mustang'), ('year', 1964.0)} + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = car.items() + b = a - [('brand', 'Ford')] + assert b == {('model', 'Mustang'), ('year', 1964.0)} + def test_items_or(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - items = dishes.items() - others = items | [('juice', 'apple')] - assert others == {('spam', 500.0), ('juice', 'apple'), ('eggs', 2.0), ('sausage', 1.0), ('bacon', 1.0)} - others = [('juice', 'raisin')] | items - assert others == {('spam', 500.0), ('juice', 'raisin'), ('eggs', 2.0), ('sausage', 1.0), ('bacon', 1.0)} + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + items = dishes.items() + others = items | [('juice', 'apple')] + assert others == {('spam', 500.0), ('juice', 'apple'), ('eggs', 2.0), ('sausage', 1.0), ('bacon', 1.0)} + others = [('juice', 'raisin')] | items + assert others == {('spam', 500.0), ('juice', 'raisin'), ('eggs', 2.0), ('sausage', 1.0), ('bacon', 1.0)} + def test_items_xor(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - items = dishes.items() - others = items ^ {('eggs', 2)} - assert others == {('spam', 500), ('bacon', 1), ('sausage', 1)} + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + items = dishes.items() + others = items ^ {('eggs', 2)} + assert others == {('spam', 500), ('bacon', 1), ('sausage', 1)} + def test_items_intersect_one_own_smaller(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - items = dishes.items() - b = items & {('eggs', 2), ('bacon', 1), ('salad', 12), ('jam', 34)} - c = {('eggs', 2), ('bacon', 1), ('salad', 12), ('jam', 34)} & items - assert b == {('bacon', 1), ('eggs', 2)} == c - -def test_items_intersect_one(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - items = dishes.items() - b = items & {('eggs', 2)} - assert b == {('eggs', 2)} - -def test_items_intersect_none(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - items = dishes.items() - b = items & {('ketchup', 12)} - assert str(b) == "set()" + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + items = dishes.items() + b = items & {('eggs', 2), ('bacon', 1), ('salad', 12), ('jam', 34)} + c = {('eggs', 2), ('bacon', 1), ('salad', 12), ('jam', 34)} & items + assert b == {('bacon', 1), ('eggs', 2)} == c + + +def test_items_intersect_one(): + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + items = dishes.items() + b = items & {('eggs', 2)} + assert b == {('eggs', 2)} + + +def test_items_intersect_none(): + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + items = dishes.items() + b = items & {('ketchup', 12)} + assert str(b) == "set()" + def test_items_len(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - items = dishes.items() - assert len(items) == 4 + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + items = dishes.items() + assert len(items) == 4 # TODO tuple support -#def test_items_contains(): +# def test_items_contains(): # dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") # items = dishes.items() # assert {('eggs', 2)} in items - + + def test_items_update_object_updates_the_items(): - employee = pm.eval("({'name': 'Phil', 'age': 22})") - dictionaryItems = employee.items() - employee.update({('salary', 3500.0)}) - assert str(dictionaryItems) == "dict_items([('name', 'Phil'), ('age', 22.0), ('salary', 3500.0)])" - + employee = pm.eval("({'name': 'Phil', 'age': 22})") + dictionaryItems = employee.items() + employee.update({('salary', 3500.0)}) + assert str(dictionaryItems) == "dict_items([('name', 'Phil'), ('age', 22.0), ('salary', 3500.0)])" + + def test_items_mapping(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - items = dishes.items() - assert str(items.mapping) == "{'eggs': 2.0, 'sausage': 1.0, 'bacon': 1.0, 'spam': 500.0}" - assert items.mapping['spam'] == 500 + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + items = dishes.items() + assert str(items.mapping) == "{'eggs': 2.0, 'sausage': 1.0, 'bacon': 1.0, 'spam': 500.0}" + assert items.mapping['spam'] == 500 + +# get method + + +def test_get_method(): + dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") + assert dishes.get('eggs') == 2 + +# get method shadowing -#get method -def test_get_method(): - dishes = pm.eval("({'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500})") - assert dishes.get('eggs') == 2 -#get method shadowing def test_method_shadowing(): - jsObj = pm.eval("({get: 'value'})") - assert repr(jsObj.get).__contains__(" { return dict; }")(d) - assert d is proxy_d + d = {"a": 1} + proxy_d = pm.eval("(dict) => { return dict; }")(d) + assert d is proxy_d + def test_eval_dicts_subdicts(): - d = {"a":1, "b":{"c": 2}} - - assert pm.eval("(dict) => { return dict.a; }")(d) == 1.0 - assert pm.eval("(dict) => { return dict.b; }")(d) is d["b"] - assert pm.eval("(dict) => { return dict.b.c; }")(d) == 2.0 + d = {"a": 1, "b": {"c": 2}} + + assert pm.eval("(dict) => { return dict.a; }")(d) == 1.0 + assert pm.eval("(dict) => { return dict.b; }")(d) is d["b"] + assert pm.eval("(dict) => { return dict.b.c; }")(d) == 2.0 + def test_eval_dicts_cycle(): - d: dict = {"a":1, "b":2} - d["recursive"] = d - - assert pm.eval("(dict) => { return dict.a; }")(d) == 1.0 - assert pm.eval("(dict) => { return dict.b; }")(d) == 2.0 - assert pm.eval("(dict) => { return dict.recursive; }")(d) is d["recursive"] + d: dict = {"a": 1, "b": 2} + d["recursive"] = d + + assert pm.eval("(dict) => { return dict.a; }")(d) == 1.0 + assert pm.eval("(dict) => { return dict.b; }")(d) == 2.0 + assert pm.eval("(dict) => { return dict.recursive; }")(d) is d["recursive"] + def test_eval_objects(): - pyObj = pm.eval("Object({a:1.0})") - assert pyObj == {'a':1.0} + pyObj = pm.eval("Object({a:1.0})") + assert pyObj == {'a': 1.0} + def test_eval_objects_subobjects(): - pyObj = pm.eval("Object({a:1.0, b:{c:2.0}})") + pyObj = pm.eval("Object({a:1.0, b:{c:2.0}})") + + assert pyObj['a'] == 1.0 + assert pyObj['b'] == {'c': 2.0} + assert pyObj['b']['c'] == 2.0 - assert pyObj['a'] == 1.0 - assert pyObj['b'] == {'c': 2.0} - assert pyObj['b']['c'] == 2.0 def test_eval_objects_cycle(): - pyObj = pm.eval("Object({a:1.0, b:2.0, recursive: function() { this.recursive = this; return this; }}.recursive())") - - assert pyObj['a'] == 1.0 - assert pyObj['b'] == 2.0 - assert pyObj['recursive'] == pyObj + pyObj = pm.eval("Object({a:1.0, b:2.0, recursive: function() { this.recursive = this; return this; }}.recursive())") + + assert pyObj['a'] == 1.0 + assert pyObj['b'] == 2.0 + assert pyObj['recursive'] == pyObj + def test_eval_objects_proxy_get(): - f = pm.eval("(obj) => { return obj.a}") - assert f({'a':42.0}) == 42.0 + f = pm.eval("(obj) => { return obj.a}") + assert f({'a': 42.0}) == 42.0 + def test_eval_objects_proxy_set(): - f = pm.eval("(obj) => { obj.a = 42.0; return;}") - pyObj = {} - f(pyObj) - assert pyObj['a'] == 42.0 - + f = pm.eval("(obj) => { obj.a = 42.0; return;}") + pyObj = {} + f(pyObj) + assert pyObj['a'] == 42.0 + + def test_eval_objects_proxy_keys(): - f = pm.eval("(obj) => { return Object.keys(obj)[0]}") - assert f({'a':42.0}) == 'a' + f = pm.eval("(obj) => { return Object.keys(obj)[0]}") + assert f({'a': 42.0}) == 'a' + def test_eval_objects_proxy_delete(): - f = pm.eval("(obj) => { delete obj.a }") - pyObj = {'a': 42.0} - f(pyObj) - assert 'a' not in pyObj + f = pm.eval("(obj) => { delete obj.a }") + pyObj = {'a': 42.0} + f(pyObj) + assert 'a' not in pyObj + def test_eval_objects_proxy_has(): - f = pm.eval("(obj) => { return 'a' in obj }") - pyObj = {'a': 42.0} - assert(f(pyObj)) + f = pm.eval("(obj) => { return 'a' in obj }") + pyObj = {'a': 42.0} + assert (f(pyObj)) + def test_eval_objects_proxy_not_extensible(): - assert False == pm.eval("(o) => Object.isExtensible(o)")({}) - assert False == pm.eval("(o) => Object.isExtensible(o)")({ "abc": 1 }) - assert True == pm.eval("(o) => Object.preventExtensions(o) === o")({}) + assert not pm.eval("(o) => Object.isExtensible(o)")({}) + assert not pm.eval("(o) => Object.isExtensible(o)")({"abc": 1}) + assert pm.eval("(o) => Object.preventExtensions(o) === o")({}) + def test_eval_objects_jsproxy_contains(): - a = pm.eval("({'c':5})") - assert 'c' in a + a = pm.eval("({'c':5})") + assert 'c' in a + def test_eval_objects_jsproxy_does_not_contain(): - a = pm.eval("({'c':5})") - assert not(4 in a) + a = pm.eval("({'c':5})") + assert not (4 in a) + def test_eval_objects_jsproxy_does_not_contain_value(): - a = pm.eval("({'c':5})") - assert not(5 in a) + a = pm.eval("({'c':5})") + assert not (5 in a) + def test_eval_objects_jsproxy_or(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") b = pm.eval("({'d':6})") c = a | b - assert a == {'c': 5.0} - assert c == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0} + assert c == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_or_true_dict_right(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") b = {'d': 6.0} c = a | b - assert a == {'c': 5.0} - assert c == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0} + assert c == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_or_true_dict_left(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less - a = {'c':5} + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + a = {'c': 5} b = pm.eval("({'d':6})") c = a | b - assert a == {'c': 5.0} - assert c == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0} + assert c == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_inplace_or(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") b = pm.eval("({'d':6})") a |= b - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_inplace_or_true_dict_right(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") - b = {'d':6.0} + b = {'d': 6.0} a |= b - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_inplace_or_true_dict_left(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less - a = {'c':5.0} + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + a = {'c': 5.0} b = pm.eval("({'d':6})") a |= b - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} - assert True == pm.eval("(o) => Object.preventExtensions(o) === o")({ "abc": 1 }) + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + assert pm.eval("(o) => Object.preventExtensions(o) === o")({"abc": 1}) + def test_instanceof_object(): - a = {'c':5.0} - result = [None] - pm.eval("(result, obj) => {result[0] = obj instanceof Object}")(result, a) - assert result[0] == True + a = {'c': 5.0} + result = [None] + pm.eval("(result, obj) => {result[0] = obj instanceof Object}")(result, a) + assert result[0] + +# iter + -#iter def test_eval_objects_proxy_iterate(): - obj = pm.eval("({ a: 123, b: 'test' })") - result = [] - for i in obj: - result.append(i) - assert result == ['a', 'b'] + obj = pm.eval("({ a: 123, b: 'test' })") + result = [] + for i in obj: + result.append(i) + assert result == ['a', 'b'] + def test_eval_objects_proxy_min(): - obj = pm.eval("({ a: 123, b: 'test' })") - assert min(obj) == 'a' + obj = pm.eval("({ a: 123, b: 'test' })") + assert min(obj) == 'a' + def test_eval_objects_proxy_max(): - obj = pm.eval("({ a: 123, b: 'test' })") - assert max(obj) == 'b' + obj = pm.eval("({ a: 123, b: 'test' })") + assert max(obj) == 'b' + def test_eval_objects_proxy_repr(): - obj = pm.eval("({ a: 123, b: 'test' , c: { d: 1 }})") - obj.e = obj # supporting circular references - expected = "{'a': 123.0, 'b': 'test', 'c': {'d': 1.0}, 'e': {...}}" - assert repr(obj) == expected - assert str(obj) == expected + obj = pm.eval("({ a: 123, b: 'test' , c: { d: 1 }})") + obj.e = obj # supporting circular references + expected = "{'a': 123.0, 'b': 'test', 'c': {'d': 1.0}, 'e': {...}}" + assert repr(obj) == expected + assert str(obj) == expected + def test_eval_objects_proxy_dict_conversion(): - obj = pm.eval("({ a: 123, b: 'test' , c: { d: 1 }})") - d = dict(obj) - assert type(obj) is not dict # dict subclass - assert type(d) is dict # strict dict - assert repr(d) == "{'a': 123.0, 'b': 'test', 'c': {'d': 1.0}}" - assert str(obj.keys()) == "dict_keys(['a', 'b', 'c'])" - assert obj == d + obj = pm.eval("({ a: 123, b: 'test' , c: { d: 1 }})") + d = dict(obj) + assert type(obj) is not dict # dict subclass + assert type(d) is dict # strict dict + assert repr(d) == "{'a': 123.0, 'b': 'test', 'c': {'d': 1.0}}" + assert str(obj.keys()) == "dict_keys(['a', 'b', 'c'])" + assert obj == d + def test_eval_objects_jsproxy_get(): - proxy = pm.eval("({a: 1})") - assert 1.0 == proxy['a'] - assert 1.0 == proxy.a + proxy = pm.eval("({a: 1})") + assert 1.0 == proxy['a'] + assert 1.0 == proxy.a + def test_eval_objects_jsproxy_set(): - proxy = pm.eval("({a: 1})") - proxy.a = 2.0 - assert 2.0 == proxy['a'] - proxy['a'] = 3.0 - assert 3.0 == proxy.a - proxy.b = 1.0 - assert 1.0 == proxy['b'] - proxy['b'] = 2.0 - assert 2.0 == proxy.b + proxy = pm.eval("({a: 1})") + proxy.a = 2.0 + assert 2.0 == proxy['a'] + proxy['a'] = 3.0 + assert 3.0 == proxy.a + proxy.b = 1.0 + assert 1.0 == proxy['b'] + proxy['b'] = 2.0 + assert 2.0 == proxy.b + def test_eval_objects_jsproxy_length(): - proxy = pm.eval("({a: 1, b:2})") - assert 2 == len(proxy) + proxy = pm.eval("({a: 1, b:2})") + assert 2 == len(proxy) + def test_eval_objects_jsproxy_delete(): - proxy = pm.eval("({a: 1})") - del proxy.a - assert None == proxy.a - assert None == proxy['a'] + proxy = pm.eval("({a: 1})") + del proxy.a + assert None is proxy.a + assert None is proxy['a'] + def test_eval_objects_jsproxy_compare(): - proxy = pm.eval("({a: 1, b:2})") - assert proxy == {'a': 1.0, 'b': 2.0} + proxy = pm.eval("({a: 1, b:2})") + assert proxy == {'a': 1.0, 'b': 2.0} + def test_eval_objects_jsproxy_contains(): - a = pm.eval("({'c':5})") - assert 'c' in a + a = pm.eval("({'c':5})") + assert 'c' in a + def test_eval_objects_jsproxy_does_not_contain(): - a = pm.eval("({'c':5})") - assert not(4 in a) + a = pm.eval("({'c':5})") + assert not (4 in a) + def test_eval_objects_jsproxy_does_not_contain_value(): - a = pm.eval("({'c':5})") - assert not(5 in a) + a = pm.eval("({'c':5})") + assert not (5 in a) + def test_eval_objects_jsproxy_or(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") b = pm.eval("({'d':6})") c = a | b - assert a == {'c': 5.0} - assert c == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0} + assert c == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_or_true_dict_right(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") b = {'d': 6.0} c = a | b - assert a == {'c': 5.0} - assert c == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0} + assert c == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_or_true_dict_left(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less - a = {'c':5} + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + a = {'c': 5} b = pm.eval("({'d':6})") c = a | b - assert a == {'c': 5.0} - assert c == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0} + assert c == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_inplace_or(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") b = pm.eval("({'d':6})") a |= b - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_inplace_or_true_dict_right(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = pm.eval("({'c':5})") - b = {'d':6.0} + b = {'d': 6.0} a |= b - assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} + assert a == {'c': 5.0, 'd': 6.0} + assert b == {'d': 6.0} + def test_eval_objects_jsproxy_inplace_or_true_dict_left(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less - a = {'c':5.0} + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + a = {'c': 5.0} b = pm.eval("({'d':6})") a |= b - assert a == {'c': 5.0, 'd': 6.0} + assert a == {'c': 5.0, 'd': 6.0} assert b == {'d': 6.0} + def test_eval_objects_jsproxy_inplace_or_true_dict_left_iterable_right(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less + if sys.version_info[0] >= 3 and sys.version_info[1] >= 9: # | is not implemented for dicts in 3.8 or less a = {'c': 5} a |= [('y', 3), ('z', 0)] - assert a == {'c': 5, 'y': 3, 'z':0} + assert a == {'c': 5, 'y': 3, 'z': 0} + def test_update_inplace_or_iterable_wrong_type(): - car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') - a = [1,2] - try: - car |= a - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "cannot convert dictionary update sequence element #0 to a sequence" + car = pm.eval('({"brand": "Ford","model": "Mustang","year": 1964})') + a = [1, 2] + try: + car |= a + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "cannot convert dictionary update sequence element #0 to a sequence" + def test_instanceof_object(): - items = {'a': 10} - result = [None] - pm.eval("(result, obj) => {result[0] = obj instanceof Object}")(result, items) - assert result[0] == True + items = {'a': 10} + result = [None] + pm.eval("(result, obj) => {result[0] = obj instanceof Object}")(result, items) + assert result[0] + def test_not_instanceof_string(): - items = {'a': 10} - result = [None] - pm.eval("(result, obj) => {result[0] = obj instanceof String}")(result, items) - assert result[0] == False + items = {'a': 10} + result = [None] + pm.eval("(result, obj) => {result[0] = obj instanceof String}")(result, items) + assert not result[0] + +# valueOf + -#valueOf def test_valueOf(): - items = {'a': 10} - result = [0] - pm.eval("(result, obj) => {result[0] = obj.valueOf()}")(result, items) - assert items == {'a': 10} - assert result[0] is items + items = {'a': 10} + result = [0] + pm.eval("(result, obj) => {result[0] = obj.valueOf()}")(result, items) + assert items == {'a': 10} + assert result[0] is items # toString + + def test_toString(): - items = {'a': 10} - result = [0] - pm.eval("(result, obj) => {result[0] = obj.toString()}")(result, items) - assert result[0] == '[object Object]' + items = {'a': 10} + result = [0] + pm.eval("(result, obj) => {result[0] = obj.toString()}")(result, items) + assert result[0] == '[object Object]' # toLocaleString + + def test_toLocaleString(): - items = {'a': 10} - result = [0] - pm.eval("(result, obj) => {result[0] = obj.toLocaleString()}")(result, items) - assert result[0] == '[object Object]' - -#repr + items = {'a': 10} + result = [0] + pm.eval("(result, obj) => {result[0] = obj.toLocaleString()}")(result, items) + assert result[0] == '[object Object]' + +# repr + + def test_repr_max_recursion_depth(): - subprocess.check_call('npm install crypto-js', shell=True) - CryptoJS = pm.require('crypto-js') - assert str(CryptoJS).__contains__("{'lib': {'Base': {'extend':") - - -#__class__ -def test___class__attribute(): - items = pm.eval("({'a': 10})") - assert repr(items.__class__) == "" - -#none value attribute -def test___none__attribute(): - a = pm.eval("({'0': 1, '1': 2})") - assert a[2] is None \ No newline at end of file + subprocess.check_call('npm install crypto-js', shell=True) + CryptoJS = pm.require('crypto-js') + assert str(CryptoJS).__contains__("{'lib': {'Base': {'extend':") + + +# __class__ +def test___class__attribute(): + items = pm.eval("({'a': 10})") + assert repr(items.__class__) == "" + +# none value attribute + + +def test___none__attribute(): + a = pm.eval("({'0': 1, '1': 2})") + assert a[2] is None diff --git a/tests/python/test_event_loop.py b/tests/python/test_event_loop.py index d0db99c3..2afbc0db 100644 --- a/tests/python/test_event_loop.py +++ b/tests/python/test_event_loop.py @@ -2,283 +2,348 @@ import pythonmonkey as pm import asyncio + def test_setTimeout_unref(): - async def async_fn(): - obj = {'val': 0} - pm.eval("""(obj) => { - setTimeout(()=>{ obj.val = 2 }, 1000).ref().ref().unref().ref().unref().unref(); + async def async_fn(): + obj = {'val': 0} + pm.eval("""(obj) => { + setTimeout(()=>{ obj.val = 2 }, 1000).ref().ref().unref().ref().unref().unref(); // chaining, no use on the first two ref calls since it's already refed initially setTimeout(()=>{ obj.val = 1 }, 100); }""")(obj) - await pm.wait() # we shouldn't wait until the first timer is fired since it's currently unrefed - assert obj['val'] == 1 + await pm.wait() # we shouldn't wait until the first timer is fired since it's currently unrefed + assert obj['val'] == 1 + + # making sure the async_fn is run + return True + assert asyncio.run(async_fn()) - # making sure the async_fn is run - return True - assert asyncio.run(async_fn()) def test_setInterval_unref(): - async def async_fn(): - obj = {'val': 0} - pm.eval("""(obj) => { + async def async_fn(): + obj = {'val': 0} + pm.eval("""(obj) => { setInterval(()=>{ obj.val++ }, 200).unref() setTimeout(()=>{ }, 500) }""")(obj) - await pm.wait() # It should stop after the setTimeout timer's 500ms. - assert obj['val'] == 2 # The setInterval timer should only run twice (500 // 200 == 2) - return True - assert asyncio.run(async_fn()) + await pm.wait() # It should stop after the setTimeout timer's 500ms. + assert obj['val'] == 2 # The setInterval timer should only run twice (500 // 200 == 2) + return True + assert asyncio.run(async_fn()) + def test_finished_timer_ref(): - async def async_fn(): - # Making sure the event-loop won't be activated again when a finished timer gets re-refed. - pm.eval(""" + async def async_fn(): + # Making sure the event-loop won't be activated again when a finished timer gets re-refed. + pm.eval(""" const timer = setTimeout(()=>{}, 100); setTimeout(()=>{ timer.ref() }, 200); """) - await pm.wait() - return True - assert asyncio.run(async_fn()) + await pm.wait() + return True + assert asyncio.run(async_fn()) + def test_set_clear_timeout(): - # throw RuntimeError outside a coroutine - with pytest.raises(RuntimeError, match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): - pm.eval("setTimeout")(print) - - async def async_fn(): - # standalone `setTimeout` - loop = asyncio.get_running_loop() - f0 = loop.create_future() - def add(a, b, c): - f0.set_result(a + b + c) - pm.eval("setTimeout")(add, 0, 1, 2, 3) - assert 6.0 == await f0 - - # test `clearTimeout` - f1 = loop.create_future() - def to_raise(msg): - f1.set_exception(TypeError(msg)) - timeout_id0 = pm.eval("setTimeout")(to_raise, 100, "going to be there") - # `setTimeout` should return a `Timeout` instance wrapping a positive integer value - assert pm.eval("(t) => t instanceof setTimeout.Timeout")(timeout_id0) - assert pm.eval("(t) => Number(t) > 0")(timeout_id0) - assert pm.eval("(t) => Number.isInteger(Number(t))")(timeout_id0) - with pytest.raises(TypeError, match="going to be there"): - await f1 # `clearTimeout` not called - f1 = loop.create_future() - timeout_id1 = pm.eval("setTimeout")(to_raise, 100, "shouldn't be here") - pm.eval("clearTimeout")(timeout_id1) - with pytest.raises(asyncio.exceptions.TimeoutError): - await asyncio.wait_for(f1, timeout=0.5) # `clearTimeout` is called - - # `this` value in `setTimeout` callback should be the global object, as spec-ed - assert await pm.eval("new Promise(function (resolve) { setTimeout(function(){ resolve(this == globalThis) }) })") - # `setTimeout` should allow passing additional arguments to the callback, as spec-ed - assert 3.0 == await pm.eval("new Promise((resolve) => setTimeout(function(){ resolve(arguments.length) }, 100, 90, 91, 92))") - assert 92.0 == await pm.eval("new Promise((resolve) => setTimeout((...args) => { resolve(args[2]) }, 100, 90, 91, 92))") - # test `setTimeout` setting delay to 0 if < 0 - await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, 0))"), timeout=0.05) - await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, -10000))"), timeout=0.05) # won't be precisely 0s - # test `setTimeout` accepting string as the delay, coercing to a number. - # Number('100') -> 100, pass if the actual delay is > 90ms and < 150ms - await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, '100'))"), timeout=0.15) # won't be precisely 100ms - with pytest.raises(asyncio.exceptions.TimeoutError): - await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, '100'))"), timeout=0.09) - # Number("1 second") -> NaN -> delay turns to be 0s - await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, '1 second'))"), timeout=0.5) # won't be precisely 0s - - # passing an invalid ID to `clearTimeout` should silently do nothing; no exception is thrown. - pm.eval("clearTimeout(NaN)") - pm.eval("clearTimeout(999)") - pm.eval("clearTimeout(-1)") - pm.eval("clearTimeout('a')") - pm.eval("clearTimeout(undefined)") - pm.eval("clearTimeout()") - - # passing a `code` string to `setTimeout` as the callback function - assert "code string" == await pm.eval(""" + # throw RuntimeError outside a coroutine + with pytest.raises(RuntimeError, + match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): + pm.eval("setTimeout")(print) + + async def async_fn(): + # standalone `setTimeout` + loop = asyncio.get_running_loop() + f0 = loop.create_future() + + def add(a, b, c): + f0.set_result(a + b + c) + pm.eval("setTimeout")(add, 0, 1, 2, 3) + assert 6.0 == await f0 + + # test `clearTimeout` + f1 = loop.create_future() + + def to_raise(msg): + f1.set_exception(TypeError(msg)) + timeout_id0 = pm.eval("setTimeout")(to_raise, 100, "going to be there") + # `setTimeout` should return a `Timeout` instance wrapping a positive integer value + assert pm.eval("(t) => t instanceof setTimeout.Timeout")(timeout_id0) + assert pm.eval("(t) => Number(t) > 0")(timeout_id0) + assert pm.eval("(t) => Number.isInteger(Number(t))")(timeout_id0) + with pytest.raises(TypeError, match="going to be there"): + await f1 # `clearTimeout` not called + f1 = loop.create_future() + timeout_id1 = pm.eval("setTimeout")(to_raise, 100, "shouldn't be here") + pm.eval("clearTimeout")(timeout_id1) + with pytest.raises(asyncio.exceptions.TimeoutError): + await asyncio.wait_for(f1, timeout=0.5) # `clearTimeout` is called + + # `this` value in `setTimeout` callback should be the global object, as spec-ed + assert await pm.eval("new Promise(function (resolve) { setTimeout(function(){ resolve(this == globalThis) }) })") + # `setTimeout` should allow passing additional arguments to the callback, as spec-ed + assert 3.0 == await pm.eval(""" + new Promise((resolve) => setTimeout(function(){ + resolve(arguments.length); + }, + 100, 90, 91, 92)) + """) + assert 92.0 == await pm.eval(""" + new Promise((resolve) => setTimeout((...args) => { + resolve(args[2]); + }, + 100, 90, 91, 92)) + """) + # test `setTimeout` setting delay to 0 if < 0 + await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, 0))"), timeout=0.05) + # won't be precisely 0s + await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, -10000))"), timeout=0.05) + # test `setTimeout` accepting string as the delay, coercing to a number. + # Number('100') -> 100, pass if the actual delay is > 90ms and < 150ms + # won't be precisely 100ms + await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, '100'))"), timeout=0.15) + with pytest.raises(asyncio.exceptions.TimeoutError): + await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, '100'))"), timeout=0.09) + # Number("1 second") -> NaN -> delay turns to be 0s + # won't be precisely 0s + await asyncio.wait_for(pm.eval("new Promise((resolve) => setTimeout(resolve, '1 second'))"), timeout=0.5) + + # passing an invalid ID to `clearTimeout` should silently do nothing; no exception is thrown. + pm.eval("clearTimeout(NaN)") + pm.eval("clearTimeout(999)") + pm.eval("clearTimeout(-1)") + pm.eval("clearTimeout('a')") + pm.eval("clearTimeout(undefined)") + pm.eval("clearTimeout()") + + # passing a `code` string to `setTimeout` as the callback function + assert "code string" == await pm.eval(""" new Promise((resolve) => { globalThis._resolve = resolve setTimeout("globalThis._resolve('code string'); delete globalThis._resolve", 100) }) """) - # making sure the async_fn is run - return True - assert asyncio.run(async_fn()) + # making sure the async_fn is run + return True + assert asyncio.run(async_fn()) + + # throw RuntimeError outside a coroutine (the event-loop has ended) + with pytest.raises(RuntimeError, + match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): + pm.eval("setTimeout")(print) - # throw RuntimeError outside a coroutine (the event-loop has ended) - with pytest.raises(RuntimeError, match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): - pm.eval("setTimeout")(print) def test_promises(): - # should throw RuntimeError if Promises are created outside a coroutine - create_promise = pm.eval("() => Promise.resolve(1)") - with pytest.raises(RuntimeError, match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): - create_promise() - - async def async_fn(): - create_promise() # inside a coroutine, no error - - # Python awaitables to JS Promise coercion - # 1. Python asyncio.Future to JS promise - loop = asyncio.get_running_loop() - f0 = loop.create_future() - f0.set_result(2561) - assert type(f0) == asyncio.Future - assert 2561 == await f0 - assert pm.eval("(p) => p instanceof Promise")(f0) is True - assert 2561 == await pm.eval("(p) => p")(f0) - del f0 - - # 2. Python asyncio.Task to JS promise - async def coro_fn(x): - await asyncio.sleep(0.01) - return x - task = loop.create_task(coro_fn("from a Task")) - assert type(task) == asyncio.Task - assert type(task) != asyncio.Future - assert isinstance(task, asyncio.Future) - assert "from a Task" == await task - assert pm.eval("(p) => p instanceof Promise")(task) is True - assert "from a Task" == await pm.eval("(p) => p")(task) - del task - - # 3. Python coroutine to JS promise - coro = coro_fn("from a Coroutine") - assert asyncio.iscoroutine(coro) - # assert "a Coroutine" == await coro # coroutines cannot be awaited more than once - # assert pm.eval("(p) => p instanceof Promise")(coro) is True # RuntimeError: cannot reuse already awaited coroutine - assert "from a Coroutine" == await pm.eval("(p) => (p instanceof Promise) && p")(coro) - del coro - - # JS Promise to Python awaitable coercion - assert 100 == await pm.eval("new Promise((r)=>{ r(100) })") - assert 10010 == await pm.eval("Promise.resolve(10010)") - with pytest.raises(pm.SpiderMonkeyError, match="^TypeError: (.|\\n)+ is not a constructor$"): - await pm.eval("Promise.resolve")(10086) - assert 10086 == await pm.eval("Promise.resolve.bind(Promise)")(10086) - - assert "promise returning a function" == (await pm.eval("Promise.resolve(() => { return 'promise returning a function' })"))() - assert "function 2" == (await pm.eval("Promise.resolve(x=>x)"))("function 2") - def aaa(n): - return n - ident0 = await (pm.eval("Promise.resolve.bind(Promise)")(aaa)) - assert "from aaa" == ident0("from aaa") - ident1 = await pm.eval("async (aaa) => x=>aaa(x)")(aaa) - assert "from ident1" == ident1("from ident1") - ident2 = await pm.eval("() => Promise.resolve(x=>x)")() - assert "from ident2" == ident2("from ident2") - ident3 = await pm.eval("(aaa) => Promise.resolve(x=>aaa(x))")(aaa) - assert "from ident3" == ident3("from ident3") - del aaa - - # promise returning a JS Promise that calls a Python function inside - def fn0(n): - return n + 100 - def fn1(): - return pm.eval("async x=>x")(fn0) - fn2 = await pm.eval("async (fn1) => { const fn0 = await fn1(); return Promise.resolve(x=>fn0(x)) }")(fn1) - assert 101.2 == fn2(1.2) - fn3 = await pm.eval("async (fn1) => { const fn0 = await fn1(); return Promise.resolve(async x => { return fn0(x) }) }")(fn1) - assert 101.3 == await fn3(1.3) - fn4 = await pm.eval("async (fn1) => { return Promise.resolve(async x => { const fn0 = await fn1(); return fn0(x) }) }")(fn1) - assert 101.4 == await fn4(1.4) - - # chained JS promises - assert "chained" == await (pm.eval("async () => new Promise((resolve) => resolve( Promise.resolve().then(()=>'chained') ))")()) - - # chained Python awaitables - async def a(): - await asyncio.sleep(0.01) - return "nested" - async def b(): - await asyncio.sleep(0.01) - return a() - async def c(): - await asyncio.sleep(0.01) - return b() - # JS `await` supports chaining. However, on Python-land, it actually requires `await (await (await c()))` - assert "nested" == await pm.eval("async (promise) => await promise")(c()) - assert "nested" == await pm.eval("async (promise) => await promise")(await c()) - assert "nested" == await pm.eval("async (promise) => await promise")(await (await c())) - assert "nested" == await pm.eval("async (promise) => await promise")(await (await (await c()))) - assert "nested" == await pm.eval("async (promise) => promise")(c()) - assert "nested" == await pm.eval("async (promise) => promise")(await c()) - assert "nested" == await pm.eval("async (promise) => promise")(await (await c())) - assert "nested" == await pm.eval("async (promise) => promise")(await (await (await c()))) - assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(c()) - assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(await c()) - assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(await (await c())) - assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(await (await (await c()))) - assert "nested" == await pm.eval("(promise) => promise")(c()) - assert "nested" == await pm.eval("(promise) => promise")(await c()) - assert "nested" == await pm.eval("(promise) => promise")(await (await c())) - with pytest.raises(TypeError, match="object str can't be used in 'await' expression"): - await pm.eval("(promise) => promise")(await (await (await c()))) - - # Python awaitable throwing exceptions - async def coro_to_throw0(): - await asyncio.sleep(0.01) - print([].non_exist) # type: ignore - with pytest.raises(pm.SpiderMonkeyError, match="Python AttributeError: 'list' object has no attribute 'non_exist'"): - await (pm.eval("(promise) => promise")(coro_to_throw0())) - with pytest.raises(pm.SpiderMonkeyError, match="Python AttributeError: 'list' object has no attribute 'non_exist'"): - await (pm.eval("async (promise) => promise")(coro_to_throw0())) - with pytest.raises(pm.SpiderMonkeyError, match="Python AttributeError: 'list' object has no attribute 'non_exist'"): - await (pm.eval("(promise) => Promise.resolve().then(async () => await promise)")(coro_to_throw0())) - async def coro_to_throw1(): - await asyncio.sleep(0.01) - raise TypeError("reason") - with pytest.raises(pm.SpiderMonkeyError, match="Python TypeError: reason"): - await (pm.eval("(promise) => promise")(coro_to_throw1())) - assert 'rejected ' == await pm.eval("(promise) => promise.then(()=>{}, (err)=>`rejected <${err.message}>`)")(coro_to_throw1()) - - # JS Promise throwing exceptions - with pytest.raises(pm.SpiderMonkeyError, match="nan"): - await pm.eval("Promise.reject(NaN)") # JS can throw anything - with pytest.raises(pm.SpiderMonkeyError, match="123.0"): - await (pm.eval("async () => { throw 123 }")()) - # await (pm.eval("async () => { throw {} }")()) - with pytest.raises(pm.SpiderMonkeyError, match="anything"): - await pm.eval("Promise.resolve().then(()=>{ throw 'anything' })") - # FIXME (Tom Tang): We currently handle Promise exceptions by converting the object thrown to a Python Exception object through `pyTypeFactory` - # - # await pm.eval("Promise.resolve().then(()=>{ throw {a:1,toString(){return'anything'}} })") - with pytest.raises(pm.SpiderMonkeyError, match="on line 1, column 31:\nTypeError: undefined has no properties"): # not going through the conversion - await pm.eval("Promise.resolve().then(()=>{ (undefined).prop })") - - # TODO (Tom Tang): Modify this testcase once we support ES2020-style dynamic import - # pm.eval("import('some_module')") # dynamic import returns a Promise, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import - with pytest.raises(pm.SpiderMonkeyError, match="\nError: Dynamic module import is disabled or not supported in this context"): - await pm.eval("import('some_module')") - # TODO (Tom Tang): properly test unhandled rejection - - # await scheduled jobs on the Python event-loop - js_sleep = pm.eval("(seconds) => new Promise((resolve) => setTimeout(resolve, seconds*1000))") - def py_sleep(seconds): # asyncio.sleep has issues on Python 3.8 - loop = asyncio.get_running_loop() - future = loop.create_future() - loop.call_later(seconds, lambda:future.set_result(None)) - return future - both_sleep = pm.eval("(js_sleep, py_sleep) => async (seconds) => { await js_sleep(seconds); await py_sleep(seconds) }")(js_sleep, py_sleep) - await asyncio.wait_for(both_sleep(0.1), timeout=0.3) # won't be precisely 0.2s - with pytest.raises(asyncio.exceptions.TimeoutError): - await asyncio.wait_for(both_sleep(0.1), timeout=0.19) - - # making sure the async_fn is run - return True - assert asyncio.run(async_fn()) - - # should throw a RuntimeError if created outside a coroutine (the event-loop has ended) - with pytest.raises(RuntimeError, match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): - pm.eval("new Promise(() => { })") + # should throw RuntimeError if Promises are created outside a coroutine + create_promise = pm.eval("() => Promise.resolve(1)") + with pytest.raises(RuntimeError, + match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): + create_promise() + + async def async_fn(): + create_promise() # inside a coroutine, no error + + # Python awaitables to JS Promise coercion + # 1. Python asyncio.Future to JS promise + loop = asyncio.get_running_loop() + f0 = loop.create_future() + f0.set_result(2561) + + assert type(f0) is asyncio.Future + assert 2561 == await f0 + assert pm.eval("(p) => p instanceof Promise")(f0) is True + assert 2561 == await pm.eval("(p) => p")(f0) + del f0 + + # 2. Python asyncio.Task to JS promise + async def coro_fn(x): + await asyncio.sleep(0.01) + return x + task = loop.create_task(coro_fn("from a Task")) + assert type(task) is asyncio.Task + assert type(task) is not asyncio.Future + assert isinstance(task, asyncio.Future) + assert "from a Task" == await task + assert pm.eval("(p) => p instanceof Promise")(task) is True + assert "from a Task" == await pm.eval("(p) => p")(task) + del task + + # 3. Python coroutine to JS promise + coro = coro_fn("from a Coroutine") + assert asyncio.iscoroutine(coro) + # assert "a Coroutine" == await coro # coroutines cannot be awaited more than once + # assert pm.eval("(p) => p instanceof Promise")(coro) # RuntimeError: cannot reuse already awaited coroutine + assert "from a Coroutine" == await pm.eval("(p) => (p instanceof Promise) && p")(coro) + del coro + + # JS Promise to Python awaitable coercion + assert 100 == await pm.eval("new Promise((r)=>{ r(100) })") + assert 10010 == await pm.eval("Promise.resolve(10010)") + with pytest.raises(pm.SpiderMonkeyError, match="^TypeError: (.|\\n)+ is not a constructor$"): + await pm.eval("Promise.resolve")(10086) + assert 10086 == await pm.eval("Promise.resolve.bind(Promise)")(10086) + + assert "promise returning a function" == (await pm.eval(""" + Promise.resolve(() => { + return 'promise returning a function'; + }); + """))() + assert "function 2" == (await pm.eval("Promise.resolve(x=>x)"))("function 2") + + def aaa(n): + return n + ident0 = await (pm.eval("Promise.resolve.bind(Promise)")(aaa)) + assert "from aaa" == ident0("from aaa") + ident1 = await pm.eval("async (aaa) => x=>aaa(x)")(aaa) + assert "from ident1" == ident1("from ident1") + ident2 = await pm.eval("() => Promise.resolve(x=>x)")() + assert "from ident2" == ident2("from ident2") + ident3 = await pm.eval("(aaa) => Promise.resolve(x=>aaa(x))")(aaa) + assert "from ident3" == ident3("from ident3") + del aaa + + # promise returning a JS Promise that calls a Python function inside + def fn0(n): + return n + 100 + + def fn1(): + return pm.eval("async x=>x")(fn0) + fn2 = await pm.eval("async (fn1) => { const fn0 = await fn1(); return Promise.resolve(x=>fn0(x)) }")(fn1) + assert 101.2 == fn2(1.2) + fn3 = await pm.eval(""" + async (fn1) => { + const fn0 = await fn1(); + return Promise.resolve(async x => { return fn0(x); }); + } + """)(fn1) + assert 101.3 == await fn3(1.3) + fn4 = await pm.eval(""" + async (fn1) => { + return Promise.resolve(async x => { + const fn0 = await fn1(); + return fn0(x); + }); + } + """)(fn1) + assert 101.4 == await fn4(1.4) + + # chained JS promises + assert "chained" == await pm.eval(""" + async () => new Promise((resolve) => resolve( Promise.resolve().then(()=>'chained') )) + """)() + + # chained Python awaitables + async def a(): + await asyncio.sleep(0.01) + return "nested" + + async def b(): + await asyncio.sleep(0.01) + return a() + + async def c(): + await asyncio.sleep(0.01) + return b() + # JS `await` supports chaining. However, on Python-land, it actually requires `await (await (await c()))` + assert "nested" == await pm.eval("async (promise) => await promise")(c()) + assert "nested" == await pm.eval("async (promise) => await promise")(await c()) + assert "nested" == await pm.eval("async (promise) => await promise")(await (await c())) + assert "nested" == await pm.eval("async (promise) => await promise")(await (await (await c()))) + assert "nested" == await pm.eval("async (promise) => promise")(c()) + assert "nested" == await pm.eval("async (promise) => promise")(await c()) + assert "nested" == await pm.eval("async (promise) => promise")(await (await c())) + assert "nested" == await pm.eval("async (promise) => promise")(await (await (await c()))) + assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(c()) + assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(await c()) + assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(await (await c())) + assert "nested" == await pm.eval("(promise) => Promise.resolve(promise)")(await (await (await c()))) + assert "nested" == await pm.eval("(promise) => promise")(c()) + assert "nested" == await pm.eval("(promise) => promise")(await c()) + assert "nested" == await pm.eval("(promise) => promise")(await (await c())) + with pytest.raises(TypeError, match="object str can't be used in 'await' expression"): + await pm.eval("(promise) => promise")(await (await (await c()))) + + # Python awaitable throwing exceptions + async def coro_to_throw0(): + await asyncio.sleep(0.01) + print([].non_exist) # type: ignore + with pytest.raises(pm.SpiderMonkeyError, match="Python AttributeError: 'list' object has no attribute 'non_exist'"): + await (pm.eval("(promise) => promise")(coro_to_throw0())) + with pytest.raises(pm.SpiderMonkeyError, match="Python AttributeError: 'list' object has no attribute 'non_exist'"): + await (pm.eval("async (promise) => promise")(coro_to_throw0())) + with pytest.raises(pm.SpiderMonkeyError, match="Python AttributeError: 'list' object has no attribute 'non_exist'"): + await (pm.eval("(promise) => Promise.resolve().then(async () => await promise)")(coro_to_throw0())) + + async def coro_to_throw1(): + await asyncio.sleep(0.01) + raise TypeError("reason") + with pytest.raises(pm.SpiderMonkeyError, match="Python TypeError: reason"): + await (pm.eval("(promise) => promise")(coro_to_throw1())) + assert 'rejected ' == await pm.eval(""" + (promise) => promise.then( + ()=>{}, + (err)=>`rejected <${err.message}>` + ) + """)(coro_to_throw1()) + + # JS Promise throwing exceptions + with pytest.raises(pm.SpiderMonkeyError, match="nan"): + await pm.eval("Promise.reject(NaN)") # JS can throw anything + with pytest.raises(pm.SpiderMonkeyError, match="123.0"): + await (pm.eval("async () => { throw 123 }")()) + # await (pm.eval("async () => { throw {} }")()) + with pytest.raises(pm.SpiderMonkeyError, match="anything"): + await pm.eval("Promise.resolve().then(()=>{ throw 'anything' })") + # FIXME (Tom Tang): We currently handle Promise exceptions by converting the object thrown to a Python Exception + # object through `pyTypeFactory + # + # await pm.eval("Promise.resolve().then(()=>{ throw {a:1,toString(){return'anything'}} })") + # not going through the conversion + with pytest.raises(pm.SpiderMonkeyError, match="on line 1, column 31:\nTypeError: undefined has no properties"): + await pm.eval("Promise.resolve().then(()=>{ (undefined).prop })") + + # TODO (Tom Tang): Modify this testcase once we support ES2020-style dynamic import + # pm.eval("import('some_module')") # dynamic import returns a Promise, see + # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import + with pytest.raises(pm.SpiderMonkeyError, + match="\nError: Dynamic module import is disabled or not supported in this context"): + await pm.eval("import('some_module')") + # TODO (Tom Tang): properly test unhandled rejection + + # await scheduled jobs on the Python event-loop + js_sleep = pm.eval("(seconds) => new Promise((resolve) => setTimeout(resolve, seconds*1000))") + + def py_sleep(seconds): # asyncio.sleep has issues on Python 3.8 + loop = asyncio.get_running_loop() + future = loop.create_future() + loop.call_later(seconds, lambda: future.set_result(None)) + return future + both_sleep = pm.eval(""" + (js_sleep, py_sleep) => async (seconds) => { + await js_sleep(seconds); + await py_sleep(seconds); + } + """)(js_sleep, py_sleep) + await asyncio.wait_for(both_sleep(0.1), timeout=0.3) # won't be precisely 0.2s + with pytest.raises(asyncio.exceptions.TimeoutError): + await asyncio.wait_for(both_sleep(0.1), timeout=0.19) + + # making sure the async_fn is run + return True + assert asyncio.run(async_fn()) + + # should throw a RuntimeError if created outside a coroutine (the event-loop has ended) + with pytest.raises(RuntimeError, + match="PythonMonkey cannot find a running Python event-loop to make asynchronous calls."): + pm.eval("new Promise(() => { })") # off-thread promises + + def test_webassembly(): - async def async_fn(): - # off-thread promises can run - assert 'instantiated' == await pm.eval(""" + async def async_fn(): + # off-thread promises can run + assert 'instantiated' == await pm.eval(""" // https://github.com/mdn/webassembly-examples/blob/main/js-api-examples/simple.wasm var code = new Uint8Array([ 0, 97, 115, 109, 1, 0, 0, 0, 1, 8, 2, 96, @@ -293,6 +358,6 @@ async def async_fn(): WebAssembly.instantiate(code, { imports: { imported_func() {} } }).then(() => 'instantiated') """) - # making sure the async_fn is run - return True - assert asyncio.run(async_fn()) \ No newline at end of file + # making sure the async_fn is run + return True + assert asyncio.run(async_fn()) diff --git a/tests/python/test_finalizationregistry.py b/tests/python/test_finalizationregistry.py index df0f2fbf..26b0a0b6 100644 --- a/tests/python/test_finalizationregistry.py +++ b/tests/python/test_finalizationregistry.py @@ -1,5 +1,6 @@ import pythonmonkey as pm + def test_finalizationregistry(): result = pm.eval(""" (collect) => { @@ -20,5 +21,3 @@ def test_finalizationregistry(): assert result[0] == 0 assert result[1] == 1 - - diff --git a/tests/python/test_functions_this.py b/tests/python/test_functions_this.py index 8f04ba06..dc20f416 100644 --- a/tests/python/test_functions_this.py +++ b/tests/python/test_functions_this.py @@ -3,10 +3,11 @@ import weakref import sys + def test_python_functions_self(): def pyFunc(param): return param - + assert 1 == pyFunc(1) assert 2 == pm.eval("""(pyFunc) => { return pyFunc(2); @@ -19,6 +20,7 @@ def pyFunc(param): } """)(pyFunc) + def test_python_methods_self(): def pyFunc(self, param): return [self, param] @@ -44,13 +46,14 @@ class Class: return jsObj.pyMethod(4); } """)(pyObj) - assert pyObj == result[0] and 4 == result[1] #pyMethod is bound to pyObj, so `self` is not `jsObj` + assert pyObj == result[0] and 4 == result[1] # pyMethod is bound to pyObj, so `self` is not `jsObj` result = pm.eval("""(pyObj) => { let pyMethod = pyObj.pyMethod; return pyMethod(5); } """)(pyObj) - assert pyObj == result[0] and 5 == result[1] #pyMethod is bound to pyObj, so `self` is not `globalThis` + assert pyObj == result[0] and 5 == result[1] # pyMethod is bound to pyObj, so `self` is not `globalThis` + def test_javascript_functions_this(): jsFunc = pm.eval(""" @@ -61,8 +64,8 @@ def test_javascript_functions_this(): class Class: pass - Class.jsFunc = jsFunc # jsFunc is not bound to Class, so `this` will be `globalThis`, not the object - + Class.jsFunc = jsFunc # jsFunc is not bound to Class, so `this` will be `globalThis`, not the object + pyObj = Class() jsObj = pm.eval("({})") jsObj.jsFunc = jsFunc @@ -89,6 +92,7 @@ class Class: """)(jsObj) assert jsObj == result[0] and 6 == result[1] + def test_JSMethodProxy_this(): jsFunc = pm.eval(""" (function(param) { @@ -98,9 +102,9 @@ def test_JSMethodProxy_this(): class Class: pass - + pyObj = Class() - pyObj.jsMethod = pm.JSMethodProxy(jsFunc, pyObj) # jsMethod is bound to pyObj, so `this` will always be `pyObj` + pyObj.jsMethod = pm.JSMethodProxy(jsFunc, pyObj) # jsMethod is bound to pyObj, so `this` will always be `pyObj` jsObj = pm.eval("({})") jsObj.jsMethod = pyObj.jsMethod globalThis = pm.eval("globalThis") @@ -119,13 +123,16 @@ class Class: """)(jsObj) assert pyObj == result[0] and 4 == result[1] -#require +# require + + def test_require_correct_this(): - subprocess.check_call('npm install crypto-js', shell=True) - CryptoJS = pm.require('crypto-js') - cipher = CryptoJS.SHA256("Hello, World!").toString() - assert cipher == "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f" - subprocess.check_call('npm uninstall crypto-js', shell=True) + subprocess.check_call('npm install crypto-js', shell=True) + CryptoJS = pm.require('crypto-js') + cipher = CryptoJS.SHA256("Hello, World!").toString() + assert cipher == "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f" + subprocess.check_call('npm uninstall crypto-js', shell=True) + def test_require_correct_this_old_style_class(): example = pm.eval(""" @@ -164,16 +171,17 @@ class Rectangle2 { return { Rectangle: Rectangle, Rectangle2: Rectangle2}; } """)() - r = pm.new(example.Rectangle)(1,2) + r = pm.new(example.Rectangle)(1, 2) assert r.getArea() == 2 assert r.getThis() == r - r2 = pm.new(example.Rectangle2)(1,2) + r2 = pm.new(example.Rectangle2)(1, 2) assert r2.getArea() == 2 assert r2.getThis() == r2 + def test_function_finalization(): ref = [] starting_ref_count = [] @@ -181,7 +189,7 @@ def test_function_finalization(): def outerScope(): def pyFunc(): return 42 - + ref.append(weakref.ref(pyFunc)) starting_ref_count.append(sys.getrefcount(pyFunc)) assert 42 == pm.eval("(func) => func()")(pyFunc) @@ -191,6 +199,6 @@ def pyFunc(): assert current_ref_count == starting_ref_count[0] + 1 outerScope() - pm.collect() # this should collect the JS proxy to pyFunc, which should decref pyFunc - #pyFunc should be collected by now - assert ref[0]() is None \ No newline at end of file + pm.collect() # this should collect the JS proxy to pyFunc, which should decref pyFunc + # pyFunc should be collected by now + assert ref[0]() is None diff --git a/tests/python/test_list_array.py b/tests/python/test_list_array.py index 20f80c87..8adb4f3d 100644 --- a/tests/python/test_list_array.py +++ b/tests/python/test_list_array.py @@ -1,39 +1,47 @@ import pythonmonkey as pm + def test_eval_array_is_list(): - pythonList = pm.eval('[]') - assert isinstance(pythonList, list) + pythonList = pm.eval('[]') + assert isinstance(pythonList, list) # extra nice but not necessary + + def test_eval_array_is_list_type_string(): - pythonListTypeString = str(type(pm.eval('[]'))) - assert pythonListTypeString == "" + pythonListTypeString = str(type(pm.eval('[]'))) + assert pythonListTypeString == "" + def test_eval_list_is_array(): - items = [1, 2, 3] - isArray = pm.eval('Array.isArray')(items) - assert isArray == True + items = [1, 2, 3] + isArray = pm.eval('Array.isArray')(items) + assert isArray + def test_typeof_array(): - items = [1, 2, 3] - result = [None] - pm.eval("(result, arr) => {result[0] = typeof arr}")(result, items) - assert result[0] == 'object' + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = typeof arr}")(result, items) + assert result[0] == 'object' + def test_instanceof_array(): - items = [1, 2, 3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr instanceof Array}")(result, items) - assert result[0] == True + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof Array}")(result, items) + assert result[0] + def test_instanceof_object(): - items = [1, 2, 3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr instanceof Object}")(result, items) - assert result[0] == True + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof Object}")(result, items) + assert result[0] + def test_not_instanceof_string(): - items = [1, 2, 3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr instanceof String}")(result, items) - assert result[0] == False \ No newline at end of file + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof String}")(result, items) + assert not result[0] diff --git a/tests/python/test_lists.py b/tests/python/test_lists.py index 42671dc6..475f873b 100644 --- a/tests/python/test_lists.py +++ b/tests/python/test_lists.py @@ -1,1058 +1,1246 @@ import pythonmonkey as pm -from functools import reduce +from functools import reduce + def test_eval_lists(): - d = [1] - proxy_d = pm.eval("(list) => { return list; }")(d) - assert d is proxy_d + d = [1] + proxy_d = pm.eval("(list) => { return list; }")(d) + assert d is proxy_d + def test_equal_lists_left_literal_right(): - pyArray = pm.eval("Array(1,2)") - assert pyArray == [1,2] + pyArray = pm.eval("Array(1,2)") + assert pyArray == [1, 2] + def test_equal_no_object_lists_left_literal_right(): - pyArray = pm.eval("[1,2]") - assert pyArray == [1,2] + pyArray = pm.eval("[1,2]") + assert pyArray == [1, 2] + def test_equal_lists_right_literal_left(): - pyArray = pm.eval("Array(1,2)") - assert [1,2] == pyArray + pyArray = pm.eval("Array(1,2)") + assert [1, 2] == pyArray + def test_equal_lists_left_list_right(): - pyArray = pm.eval("Array(1,2)") - b = [1,2] - assert pyArray == b + pyArray = pm.eval("Array(1,2)") + b = [1, 2] + assert pyArray == b + def test_equal_lists_right_literal_left(): - pyArray = pm.eval("Array(1,2)") - b = [1,2] - assert b == pyArray + pyArray = pm.eval("Array(1,2)") + b = [1, 2] + assert b == pyArray + def test_not_equal_lists_left_literal_right(): - pyArray = pm.eval("Array(1,2)") - assert pyArray != [1,3] + pyArray = pm.eval("Array(1,2)") + assert pyArray != [1, 3] + def test_smaller_lists_left_literal_right(): - pyArray = pm.eval("Array(1,2)") - assert pyArray < [1,3] + pyArray = pm.eval("Array(1,2)") + assert pyArray < [1, 3] + def test_equal_sublist(): - pyArray = pm.eval("Array(1,2,[3,4])") - assert pyArray == [1,2,[3,4]] + pyArray = pm.eval("Array(1,2,[3,4])") + assert pyArray == [1, 2, [3, 4]] + def test_not_equal_sublist_right_not_sublist(): - pyArray = pm.eval("Array(1,2,[3,4])") - assert pyArray != [1,2,3,4] + pyArray = pm.eval("Array(1,2,[3,4])") + assert pyArray != [1, 2, 3, 4] + def test_equal_self(): - a = pm.eval("([1,2,3,4])") - b = a - assert b == a + a = pm.eval("([1,2,3,4])") + b = a + assert b == a + def test_equal_other_instance(): - a = pm.eval("([1,2,3,4])") - b = [1,2,3,4] - assert b == a + a = pm.eval("([1,2,3,4])") + b = [1, 2, 3, 4] + assert b == a + def test_not_equal_size(): - a = pm.eval("([1,2,3,4])") - b = [1,2] - assert b != a + a = pm.eval("([1,2,3,4])") + b = [1, 2] + assert b != a + def test_equal_other_js_instance(): - a = pm.eval("([1,2,3,4])") - b = pm.eval("([1,2,3,4])") - assert b == a + a = pm.eval("([1,2,3,4])") + b = pm.eval("([1,2,3,4])") + assert b == a + def test_not_equal_other_js_instance(): - a = pm.eval("([1,2,3,4])") - b = pm.eval("([1,2,4,3])") - assert b != a + a = pm.eval("([1,2,3,4])") + b = pm.eval("([1,2,4,3])") + assert b != a + def test_not_smaller_self(): - a = pm.eval("([1,2,3,4])") - b = a - assert not(b < a) + a = pm.eval("([1,2,3,4])") + b = a + assert not (b < a) + def test_not_smaller_equal_self(): - a = pm.eval("([1,2,3,4])") - b = a - assert b <= a + a = pm.eval("([1,2,3,4])") + b = a + assert b <= a + def test_eval_arrays_proxy_get(): - f = pm.eval("(arr) => { return arr[0]}") - assert f([1,2]) == 1.0 + f = pm.eval("(arr) => { return arr[0]}") + assert f([1, 2]) == 1.0 + def test_eval_arrays_proxy_set(): - f = pm.eval("(arr) => { arr[0] = 42.0; return;}") - pyObj = [1] - f(pyObj) - assert pyObj[0] == 42.0 + f = pm.eval("(arr) => { arr[0] = 42.0; return;}") + pyObj = [1] + f(pyObj) + assert pyObj[0] == 42.0 + +# get + -#get def test_get(): - pyArray = pm.eval("[1,2]") - assert pyArray[0] == 1 + pyArray = pm.eval("[1,2]") + assert pyArray[0] == 1 + def test_get_negative_index(): - pyArray = pm.eval("[1,2]") - assert pyArray[-1] == 2 + pyArray = pm.eval("[1,2]") + assert pyArray[-1] == 2 + def test_get_index_out_of_range(): - pyArray = pm.eval("[1,2]") - try: - pyArray[3] - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == 'list index out of range' + pyArray = pm.eval("[1,2]") + try: + pyArray[3] + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == 'list index out of range' + def test_get_index_wrong_type_str(): - pyArray = pm.eval("[1,2]") - try: - pyArray["g"] - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == 'list indices must be integers or slices, not str' + pyArray = pm.eval("[1,2]") + try: + pyArray["g"] + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == 'list indices must be integers or slices, not str' + def test_get_index_wrong_type_list(): - pyArray = pm.eval("[1,2]") - try: - pyArray[[2]] - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == 'list indices must be integers or slices, not list' + pyArray = pm.eval("[1,2]") + try: + pyArray[[2]] + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == 'list indices must be integers or slices, not list' + def test_delete(): - pyArray = pm.eval("[1,2]") - del pyArray[0] - assert pyArray == [None, 2] + pyArray = pm.eval("[1,2]") + del pyArray[0] + assert pyArray == [None, 2] -#assign +# assign def test_assign(): - pyArray = pm.eval("[1,2]") - pyArray[0] = 6 - assert pyArray[0] == 6 + pyArray = pm.eval("[1,2]") + pyArray[0] = 6 + assert pyArray[0] == 6 + def test_assign_negative_index(): - pyArray = pm.eval("[1,2]") - pyArray[-1] = 6 - assert pyArray[1] == 6 + pyArray = pm.eval("[1,2]") + pyArray[-1] = 6 + assert pyArray[1] == 6 + def test_assign_index_out_of_range(): - pyArray = pm.eval("[1,2]") - try: - pyArray[3] = 8 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == 'list assignment index out of range' + pyArray = pm.eval("[1,2]") + try: + pyArray[3] = 8 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == 'list assignment index out of range' + def test_assign_index_wrong_type_str(): - pyArray = pm.eval("[1,2]") - try: - pyArray["g"] = 4 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == 'list indices must be integers or slices, not str' + pyArray = pm.eval("[1,2]") + try: + pyArray["g"] = 4 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == 'list indices must be integers or slices, not str' def test_assign_index_wrong_type_list(): - pyArray = pm.eval("[1,2]") - try: - pyArray[[2]] = 9 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == 'list indices must be integers or slices, not list' + pyArray = pm.eval("[1,2]") + try: + pyArray[[2]] = 9 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == 'list indices must be integers or slices, not list' -#repr +# repr def test_repr_empty_array(): - pyArray = pm.eval("[]") - expected = "[]" - assert repr(pyArray) == expected - assert str(pyArray) == expected + pyArray = pm.eval("[]") + expected = "[]" + assert repr(pyArray) == expected + assert str(pyArray) == expected + def test_repr_non_empty_array(): - pyArray = pm.eval("[1,2]") - expected = "[1.0, 2.0]" - assert repr(pyArray) == expected - assert str(pyArray) == expected + pyArray = pm.eval("[1,2]") + expected = "[1.0, 2.0]" + assert repr(pyArray) == expected + assert str(pyArray) == expected + def test_repr_recursion(): - pyArray = pm.eval(""" + pyArray = pm.eval(""" () => { let arr = [1,2]; arr.push(arr); return arr; } """)() - expected = "[1.0, 2.0, [...]]" - assert repr(pyArray) == expected - assert str(pyArray) == expected + expected = "[1.0, 2.0, [...]]" + assert repr(pyArray) == expected + assert str(pyArray) == expected + +# concat + -#concat def test_concat_wrong_type(): - likes = pm.eval('([1,2])') - try: - likes + 3 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == 'can only concatenate list (not "int") to list' + likes = pm.eval('([1,2])') + try: + likes + 3 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == 'can only concatenate list (not "int") to list' + def test_concat_empty_empty(): - pyArray = pm.eval("[]") - pyArray + [] - assert pyArray == [] + pyArray = pm.eval("[]") + pyArray + [] + assert pyArray == [] + def test_concat_not_empty_empty(): - pyArray = pm.eval("[1,2]") - pyArray + [] - assert pyArray == [1,2] + pyArray = pm.eval("[1,2]") + pyArray + [] + assert pyArray == [1, 2] + def test_concat_not_in_place(): - pyArray = pm.eval("[1,2]") - b = pyArray + [3,4] - assert pyArray == [1,2] + pyArray = pm.eval("[1,2]") + b = pyArray + [3, 4] + assert pyArray == [1, 2] # TODO -#def test_concat_array_right(): +# def test_concat_array_right(): # pyArray = pm.eval("[1,2]") # b = [3,4] + pyArray # assert b == [1,2,3,4] + def test_concat_pm_array_right(): - a = pm.eval("[1,2]") - b = pm.eval("[3,4]") - c = a + b - assert c == [1,2,3,4] + a = pm.eval("[1,2]") + b = pm.eval("[3,4]") + c = a + b + assert c == [1, 2, 3, 4] + +# repeat + -#repeat def test_repeat_negative(): - a = pm.eval("[1,2]") - b = a * -1 - assert b == [] + a = pm.eval("[1,2]") + b = a * -1 + assert b == [] + def test_repeat_zero(): - a = pm.eval("[1,2]") - b = a * 0 - assert b == [] + a = pm.eval("[1,2]") + b = a * 0 + assert b == [] + def test_repeat_once(): - a = pm.eval("[1,2]") - b = a * 1 - assert b == [1,2] - assert a is not b + a = pm.eval("[1,2]") + b = a * 1 + assert b == [1, 2] + assert a is not b + def test_repeat_twice(): - a = pm.eval("[1,2]") - b = a * 2 - assert b == [1,2,1,2] + a = pm.eval("[1,2]") + b = a * 2 + assert b == [1, 2, 1, 2] + def test_repeat_wrong_type(): - a = pm.eval('([1,2])') - try: - a * 1.0 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "can't multiply sequence by non-int of type 'float'" + a = pm.eval('([1,2])') + try: + a * 1.0 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "can't multiply sequence by non-int of type 'float'" + def test_repeat_not_in_place(): - a = pm.eval("[1,2]") - b = a * 3 - assert b == [1.0, 2.0, 1.0, 2.0, 1.0, 2.0] - assert a == [1,2] + a = pm.eval("[1,2]") + b = a * 3 + assert b == [1.0, 2.0, 1.0, 2.0, 1.0, 2.0] + assert a == [1, 2] + +# contains + -#contains def test_contains_found(): - a = pm.eval("[1,2]") - assert 1 in a + a = pm.eval("[1,2]") + assert 1 in a + def test_contains_not_found(): - a = pm.eval("[1,2]") - assert 7 not in a + a = pm.eval("[1,2]") + assert 7 not in a + def test_contains_not_found_odd_type(): - a = pm.eval("[1,2]") - assert [1] not in a + a = pm.eval("[1,2]") + assert [1] not in a + +# concat in place += + -#concat in place += def test_concat_is_inplace(): - pyArray = pm.eval("[1,2]") - pyArray += [3] - assert pyArray == [1,2,3] + pyArray = pm.eval("[1,2]") + pyArray += [3] + assert pyArray == [1, 2, 3] + def test_concat_in_place_empty_empty(): - pyArray = pm.eval("[]") - pyArray += [] - assert pyArray == [] + pyArray = pm.eval("[]") + pyArray += [] + assert pyArray == [] + def test_concat_not_empty_empty(): - pyArray = pm.eval("[1,2]") - pyArray += [] - assert pyArray == [1,2] + pyArray = pm.eval("[1,2]") + pyArray += [] + assert pyArray == [1, 2] + +# repeat in place *= + -#repeat in place *= def test_repeat_is_in_place(): - a = pm.eval("[1,2]") - a *= 3 - assert a == [1,2,1,2,1,2] + a = pm.eval("[1,2]") + a *= 3 + assert a == [1, 2, 1, 2, 1, 2] + def test_repeat_negative(): - a = pm.eval("[1,2]") - a *= -1 - assert a == [] + a = pm.eval("[1,2]") + a *= -1 + assert a == [] + def test_repeat_zero(): - a = pm.eval("[1,2]") - a *= 0 - assert a == [] + a = pm.eval("[1,2]") + a *= 0 + assert a == [] + def test_repeat_twice(): - a = pm.eval("[1,2]") - a *= 2 - assert a == [1,2,1,2] + a = pm.eval("[1,2]") + a *= 2 + assert a == [1, 2, 1, 2] + def test_repeat_wrong_type(): - a = pm.eval('([1,2])') - try: - a *= 1.7 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "can't multiply sequence by non-int of type 'float'" + a = pm.eval('([1,2])') + try: + a *= 1.7 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "can't multiply sequence by non-int of type 'float'" -#clear +# clear def test_clear(): - a = pm.eval("[1,2]") - a.clear() - assert a == [] + a = pm.eval("[1,2]") + a.clear() + assert a == [] + def test_clear_with_arg(): - a = pm.eval('([1,2])') - try: - a.clear(8) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__('clear() takes no arguments (1 given)') - -#copy + a = pm.eval('([1,2])') + try: + a.clear(8) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('clear() takes no arguments (1 given)') + +# copy + + def test_copy(): - a = pm.eval("[1,2]") - b = a.copy() - b[0] = 8 - assert a[0] == 1 - assert b[0] == 8 + a = pm.eval("[1,2]") + b = a.copy() + b[0] = 8 + assert a[0] == 1 + assert b[0] == 8 + def test_copy_with_arg(): - a = pm.eval('([1,2])') - try: - b = a.copy(8) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__('copy() takes no arguments (1 given)') - -#append + a = pm.eval('([1,2])') + try: + b = a.copy(8) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('copy() takes no arguments (1 given)') + +# append + + def test_append(): - a = pm.eval("[1,2]") - b = a.append(3) - assert a == [1,2,3] - assert b == None + a = pm.eval("[1,2]") + b = a.append(3) + assert a == [1, 2, 3] + assert b is None + +# insert + -#insert def test_insert_list(): - a = pm.eval("[1,2]") - a.insert(0, [3,4]) - assert a == [[3,4],1,2] + a = pm.eval("[1,2]") + a.insert(0, [3, 4]) + assert a == [[3, 4], 1, 2] + def test_insert_boolean(): - a = pm.eval("[1,2]") - a.insert(0, True) - assert a == [True,1,2] + a = pm.eval("[1,2]") + a.insert(0, True) + assert a == [True, 1, 2] + def test_insert_int(): - a = pm.eval("[1,2]") - a.insert(0, 3) - assert a == [3,1,2] + a = pm.eval("[1,2]") + a.insert(0, 3) + assert a == [3, 1, 2] + def test_insert_float(): - a = pm.eval("[1,2]") - a.insert(0, 4.5) - assert a == [4.5,1,2] + a = pm.eval("[1,2]") + a.insert(0, 4.5) + assert a == [4.5, 1, 2] + def test_insert_string(): - a = pm.eval("[1,2]") - a.insert(0, "Hey") - assert a == ["Hey",1,2] + a = pm.eval("[1,2]") + a.insert(0, "Hey") + assert a == ["Hey", 1, 2] + def test_insert_none(): - a = pm.eval("[1,2]") - a.insert(0, None) - assert a == [None,1,2] + a = pm.eval("[1,2]") + a.insert(0, None) + assert a == [None, 1, 2] + def test_insert_function(): - def f(): - return - a = pm.eval("[1,2]") - a.insert(0, f) - assert len(a) == 3 + def f(): + return + a = pm.eval("[1,2]") + a.insert(0, f) + assert len(a) == 3 + def test_insert_int_neg_index(): - a = pm.eval("[1,2]") - a.insert(-1, 3) - assert a == [1,3,2] + a = pm.eval("[1,2]") + a.insert(-1, 3) + assert a == [1, 3, 2] + def test_insert_int_too_large_index(): - a = pm.eval("[1,2]") - a.insert(5, 3) - assert a == [1,2,3] + a = pm.eval("[1,2]") + a.insert(5, 3) + assert a == [1, 2, 3] + def test_insert_int_too_many_args(): - a = pm.eval('([1,2])') - try: - a.insert(5,6,7) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "insert expected 2 arguments, got 3" + a = pm.eval('([1,2])') + try: + a.insert(5, 6, 7) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "insert expected 2 arguments, got 3" + def test_insert_int_too_few_args(): - a = pm.eval('([1,2])') - try: - a.insert() - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "insert expected 2 arguments, got 0" + a = pm.eval('([1,2])') + try: + a.insert() + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "insert expected 2 arguments, got 0" # extend + + def test_extend_with_list(): - a = pm.eval("[1,2]") - a.extend([3,4]) - assert a == [1,2,3,4] + a = pm.eval("[1,2]") + a.extend([3, 4]) + assert a == [1, 2, 3, 4] + def test_extend_with_dict_single(): - a = pm.eval("[1,2]") - a.extend({'key':5}) - assert a == [1,2,'key'] + a = pm.eval("[1,2]") + a.extend({'key': 5}) + assert a == [1, 2, 'key'] + def test_extend_with_dict_double(): - a = pm.eval("[1,2]") - a.extend({'key':5, 'otherKey':6}) - assert a == [1,2,'key','otherKey'] + a = pm.eval("[1,2]") + a.extend({'key': 5, 'otherKey': 6}) + assert a == [1, 2, 'key', 'otherKey'] + def test_extend_with_number(): - a = pm.eval('([1,2])') - try: - a.extend(3) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "'int' object is not iterable" + a = pm.eval('([1,2])') + try: + a.extend(3) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "'int' object is not iterable" + def test_extend_with_own_list(): - a = pm.eval("[1,2]") - a.extend(a) - assert a == [1,2,1,2] + a = pm.eval("[1,2]") + a.extend(a) + assert a == [1, 2, 1, 2] + def test_extend_with_pm_list(): - a = pm.eval("[1,2]") - b = pm.eval("[3,4]") - a.extend(b) - assert a == [1,2,3,4] + a = pm.eval("[1,2]") + b = pm.eval("[3,4]") + a.extend(b) + assert a == [1, 2, 3, 4] # TODO iterable dict + + def test_extend_with_own_dict(): - a = pm.eval("[1,2]") - b = pm.eval("({'key':5, 'key2':6})") - a.extend(b) - assert a == [1,2,"key","key2"] + a = pm.eval("[1,2]") + b = pm.eval("({'key':5, 'key2':6})") + a.extend(b) + assert a == [1, 2, "key", "key2"] + +# pop + -#pop def test_pop_no_arg(): - a = pm.eval("[1,2]") - b = a.pop() - assert a == [1] - assert b == 2 + a = pm.eval("[1,2]") + b = a.pop() + assert a == [1] + assert b == 2 + def test_pop_empty_list(): - a = pm.eval('([])') - try: - a.pop() - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "pop from empty list" + a = pm.eval('([])') + try: + a.pop() + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "pop from empty list" + def test_pop_list_no_arg(): - a = pm.eval("[1,[2,3]]") - b = a.pop() - assert a == [1] - assert b == [2,3] + a = pm.eval("[1,[2,3]]") + b = a.pop() + assert a == [1] + assert b == [2, 3] + def test_pop_first_item(): - a = pm.eval("[1,2]") - b = a.pop(0) - assert a == [2] - assert b == 1 + a = pm.eval("[1,2]") + b = a.pop(0) + assert a == [2] + assert b == 1 + def test_pop_second_item(): - a = pm.eval("[1,2]") - b = a.pop(1) - assert a == [1] - assert b == 2 + a = pm.eval("[1,2]") + b = a.pop(1) + assert a == [1] + assert b == 2 + def test_pop_negative_item(): - a = pm.eval("[1,2]") - b = a.pop(-1) - assert a == [1] - assert b == 2 + a = pm.eval("[1,2]") + b = a.pop(-1) + assert a == [1] + assert b == 2 + def test_pop_out_of_bounds_item(): - a = pm.eval('([1,2])') - try: - a.pop(3) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "pop index out of range" - -#remove + a = pm.eval('([1,2])') + try: + a.pop(3) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "pop index out of range" + +# remove + + def test_remove_found_once(): - a = pm.eval("[1,2]") - a.remove(1) - assert a == [2] + a = pm.eval("[1,2]") + a.remove(1) + assert a == [2] + def test_remove_found_twice(): - a = pm.eval("[1,2,1,2]") - a.remove(1) - assert a == [2,1,2] + a = pm.eval("[1,2,1,2]") + a.remove(1) + assert a == [2, 1, 2] + def test_remove_no_args(): - a = pm.eval('([1,2])') - try: - a.remove() - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__('remove() takes exactly one argument (0 given)') + a = pm.eval('([1,2])') + try: + a.remove() + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('remove() takes exactly one argument (0 given)') + def test_remove_not_found(): - a = pm.eval('([1,2])') - try: - a.remove(3) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "list.remove(x): x not in list" - -#index + a = pm.eval('([1,2])') + try: + a.remove(3) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "list.remove(x): x not in list" + +# index + + def test_index_found(): - a = pm.eval("[1,2,3,4]") - b = a.index(3) - assert b == 2 + a = pm.eval("[1,2,3,4]") + b = a.index(3) + assert b == 2 + def test_index_not_found(): - a = pm.eval('([1,2])') - try: - a.index(3) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "3 is not in list" + a = pm.eval('([1,2])') + try: + a.index(3) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "3 is not in list" + def test_index_no_args(): - a = pm.eval('([1,2,3,4])') - try: - a.index() - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "index expected at least 1 argument, got 0" + a = pm.eval('([1,2,3,4])') + try: + a.index() + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "index expected at least 1 argument, got 0" + def test_index_too_many_args(): - a = pm.eval('([1,2,3,4])') - try: - a.index(2,3,4,5) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "index expected at most 3 arguments, got 4" + a = pm.eval('([1,2,3,4])') + try: + a.index(2, 3, 4, 5) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "index expected at most 3 arguments, got 4" + def test_index_found_with_start(): - a = pm.eval("[1,2,3,4]") - b = a.index(3, 0) - assert b == 2 + a = pm.eval("[1,2,3,4]") + b = a.index(3, 0) + assert b == 2 + def test_index_found_with_negative_start(): - a = pm.eval("[1,2,3,4]") - b = a.index(3, -3) - assert b == 2 + a = pm.eval("[1,2,3,4]") + b = a.index(3, -3) + assert b == 2 + def test_index_not_found_with_start(): - a = pm.eval('([1,2,3,4])') - try: - a.index(3, 4) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "3 is not in list" + a = pm.eval('([1,2,3,4])') + try: + a.index(3, 4) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "3 is not in list" + def test_index_found_with_start_and_stop(): - a = pm.eval("[1,2,3,4]") - b = a.index(3,1,4) - assert b == 2 + a = pm.eval("[1,2,3,4]") + b = a.index(3, 1, 4) + assert b == 2 + def test_index_found_with_start_and_negative_stop(): - a = pm.eval("[1,2,3,4]") - b = a.index(3,1,-1) - assert b == 2 + a = pm.eval("[1,2,3,4]") + b = a.index(3, 1, -1) + assert b == 2 + def test_index_not_found_with_start_and_stop(): - a = pm.eval('([1,2,3,4])') - try: - a.index(3,4,4) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "3 is not in list" + a = pm.eval('([1,2,3,4])') + try: + a.index(3, 4, 4) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "3 is not in list" + def test_index_not_found_with_start_and_outofbounds_stop(): - a = pm.eval('([1,2,3,4])') - try: - a.index(3,4,7) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "3 is not in list" - -#count + a = pm.eval('([1,2,3,4])') + try: + a.index(3, 4, 7) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "3 is not in list" + +# count + + def test_count_found_once(): - a = pm.eval("[1,2,3,4]") - b = a.count(1) - assert b == 1 + a = pm.eval("[1,2,3,4]") + b = a.count(1) + assert b == 1 + def test_count_found_once_non_primitive_type(): - a = pm.eval("[1,2,[3,4]]") - b = a.count([3,4]) - assert b == 1 + a = pm.eval("[1,2,[3,4]]") + b = a.count([3, 4]) + assert b == 1 + def test_count_found_twice(): - a = pm.eval("[1,2,3,4,5,1,2]") - b = a.count(2) - assert b == 2 + a = pm.eval("[1,2,3,4,5,1,2]") + b = a.count(2) + assert b == 2 + def test_count_not_found(): - a = pm.eval("[1,2,3,4,5,1,2]") - b = a.count(7) - assert b == 0 + a = pm.eval("[1,2,3,4,5,1,2]") + b = a.count(7) + assert b == 0 + +# reverse + -#reverse def test_reverse(): - a = pm.eval("[1,2,3,4]") - b = a.reverse() - assert a == [4,3,2,1] - assert b == None + a = pm.eval("[1,2,3,4]") + b = a.reverse() + assert a == [4, 3, 2, 1] + assert b is None + def test_reverse_zero_length(): - a = pm.eval("[]") - a.reverse() - assert a == [] + a = pm.eval("[]") + a.reverse() + assert a == [] + def test_reverse_one_length(): - a = pm.eval("[2]") - a.reverse() - assert a == [2] + a = pm.eval("[2]") + a.reverse() + assert a == [2] + def test_reverse_too_many_args(): - a = pm.eval('([1,2,3,4])') - try: - a.reverse(3) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__('reverse() takes no arguments (1 given)') - -#sort + a = pm.eval('([1,2,3,4])') + try: + a.reverse(3) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('reverse() takes no arguments (1 given)') + +# sort + + def test_sort(): - a = pm.eval("[5,1,2,4,9,6,3,7,8]") - a.sort() - assert a == [1,2,3,4,5,6,7,8,9] + a = pm.eval("[5,1,2,4,9,6,3,7,8]") + a.sort() + assert a == [1, 2, 3, 4, 5, 6, 7, 8, 9] + def test_sort_reverse(): - a = pm.eval("[5,1,2,4,9,6,3,7,8]") - a.sort(reverse=True) - assert a == [9,8,7,6,5,4,3,2,1] + a = pm.eval("[5,1,2,4,9,6,3,7,8]") + a.sort(reverse=True) + assert a == [9, 8, 7, 6, 5, 4, 3, 2, 1] + def test_sort_reverse_false(): - a = pm.eval("[5,1,2,4,9,6,3,7,8]") - a.sort(reverse=False) - assert a == [1,2,3,4,5,6,7,8,9] + a = pm.eval("[5,1,2,4,9,6,3,7,8]") + a.sort(reverse=False) + assert a == [1, 2, 3, 4, 5, 6, 7, 8, 9] + def test_sort_with_function(): - def myFunc(e,f): - return len(e) - len(f) - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - a.sort(key=myFunc) - assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + def myFunc(e, f): + return len(e) - len(f) + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + a.sort(key=myFunc) + assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + def test_sort_with_js_function(): - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") - a.sort(key=myFunc) - assert a == ['BMW', 'Ford', 'Mitsubishi', 'VW'] + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + a.sort(key=myFunc) + assert a == ['BMW', 'Ford', 'Mitsubishi', 'VW'] + def test_sort_with_one_arg_function(): - def myFunc(e): - return len(e) - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - a.sort(key=myFunc) - assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + def myFunc(e): + return len(e) + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + a.sort(key=myFunc) + assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + def test_sort_with_one_arg_function_wrong_data_type(): - def myFunc(e): - return len(e) - a = pm.eval('([1,2,3,4])') - try: - a.sort(key=myFunc) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "object of type 'float' has no len()" + def myFunc(e): + return len(e) + a = pm.eval('([1,2,3,4])') + try: + a.sort(key=myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "object of type 'float' has no len()" + def test_sort_with_function_two_args_and_reverse_false(): - def myFunc(e,f): - return len(e) - len(f) - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - a.sort(key=myFunc, reverse=False) - assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + def myFunc(e, f): + return len(e) - len(f) + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + a.sort(key=myFunc, reverse=False) + assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + def test_sort_with_js_function_and_reverse_false(): - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") - a.sort(key=myFunc, reverse=False) - assert a == ['BMW', 'Ford', 'Mitsubishi', 'VW'] + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + a.sort(key=myFunc, reverse=False) + assert a == ['BMW', 'Ford', 'Mitsubishi', 'VW'] + def test_sort_with_function_and_reverse(): - def myFunc(e,f): - return len(e) - len(f) - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - a.sort(key=myFunc, reverse=True) - assert a == ['Mitsubishi', 'Ford', 'BMW', 'VW'] + def myFunc(e, f): + return len(e) - len(f) + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + a.sort(key=myFunc, reverse=True) + assert a == ['Mitsubishi', 'Ford', 'BMW', 'VW'] + def test_sort_with_js_function_and_reverse(): - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") - a.sort(key=myFunc, reverse=True) - assert a == ['VW', 'Mitsubishi', 'Ford', 'BMW'] + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + a.sort(key=myFunc, reverse=True) + assert a == ['VW', 'Mitsubishi', 'Ford', 'BMW'] + def test_sort_with_function_wrong_type(): - a = pm.eval('([1,2,3,4])') - try: - b = 9 - a.sort(key=b) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "'int' object is not callable" + a = pm.eval('([1,2,3,4])') + try: + b = 9 + a.sort(key=b) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "'int' object is not callable" + def test_tricky_sort(): - a = pm.eval("[6, -2, 2, -7]") - a.sort() - assert a == [-7, -2, 2, 6] + a = pm.eval("[6, -2, 2, -7]") + a.sort() + assert a == [-7, -2, 2, 6] + def test_tricky_sort_reverse(): - a = pm.eval("[6, -2, 2, -7]") - a.sort(reverse=True) - assert a == [6, 2, -2, -7] + a = pm.eval("[6, -2, 2, -7]") + a.sort(reverse=True) + assert a == [6, 2, -2, -7] + def test_sort_with_builtin_function(): # + wrong type of entries - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - a.sort(key=len) - assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + a.sort(key=len) + assert a == ['VW', 'BMW', 'Ford', 'Mitsubishi'] + def test_sort_with_builtin_function_and_reverse(): # + wrong type of entries - a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") - a.sort(key=len, reverse=True) - assert a == ['Mitsubishi', 'Ford', 'BMW', 'VW'] + a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") + a.sort(key=len, reverse=True) + assert a == ['Mitsubishi', 'Ford', 'BMW', 'VW'] + def test_sort_with_builtin_function_wrong_data_type(): - a = pm.eval('([1,2,3,4])') - try: - a.sort(key=len) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "object of type 'float' has no len()" - -#iter + a = pm.eval('([1,2,3,4])') + try: + a.sort(key=len) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "object of type 'float' has no len()" + +# iter + + def iter_min(): - a = pm.eval("([7,9,1,2,3,4,5,6])") - b = min(a) - assert b == 1 + a = pm.eval("([7,9,1,2,3,4,5,6])") + b = min(a) + assert b == 1 + def iter_max(): - a = pm.eval("([7,9,1,2,3,4,5,6])") - b = max(a) - assert b == 9 + a = pm.eval("([7,9,1,2,3,4,5,6])") + b = max(a) + assert b == 9 + def iter_for(): - a = pm.eval("(['this is a test', 'another test'])") - b = [item.upper() for item in a] - assert b == ['THIS IS A TEST', 'ANOTHER TEST'] + a = pm.eval("(['this is a test', 'another test'])") + b = [item.upper() for item in a] + assert b == ['THIS IS A TEST', 'ANOTHER TEST'] + def test_reduce(): - a = pm.eval("([1, 3, 5, 6, 2])") - result = reduce(lambda a, b: a+b, a) - assert result == 17 + a = pm.eval("([1, 3, 5, 6, 2])") + result = reduce(lambda a, b: a + b, a) + assert result == 17 + def test_iter_next(): - a = pm.eval("([1, 3, 5, 6, 2])") - iterator = iter(a) - try: - while True: - element = next(iterator) - assert(False) - except StopIteration: - assert(True) - -#reverse_iter + a = pm.eval("([1, 3, 5, 6, 2])") + iterator = iter(a) + try: + while True: + element = next(iterator) + assert (False) + except StopIteration: + assert (True) + +# reverse_iter + + def iter_reverse(): - a = pm.eval("(['7','9','1','2','3','4','5','6'])") - b = "" - for i in reversed(a): - b += i - assert b == '65432197' + a = pm.eval("(['7','9','1','2','3','4','5','6'])") + b = "" + for i in reversed(a): + b += i + assert b == '65432197' # slice subscript + + def test_slice_full_array_single_subscript(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[:] - assert b == [1,2,3,4,5,6] + a = pm.eval("([1,2,3,4,5,6])") + b = a[:] + assert b == [1, 2, 3, 4, 5, 6] + def test_slice_full_array_double_subscript(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[::] - assert b == [1,2,3,4,5,6] + a = pm.eval("([1,2,3,4,5,6])") + b = a[::] + assert b == [1, 2, 3, 4, 5, 6] + def test_slice_empty_length_left(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[len(a):] - assert b == [] + a = pm.eval("([1,2,3,4,5,6])") + b = a[len(a):] + assert b == [] + def test_slice_full_length_right(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[:len(a)] - assert b == [1,2,3,4,5,6] + a = pm.eval("([1,2,3,4,5,6])") + b = a[:len(a)] + assert b == [1, 2, 3, 4, 5, 6] + def test_slice_zero_to_length(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[0:6] - assert b == [1,2,3,4,5,6] + a = pm.eval("([1,2,3,4,5,6])") + b = a[0:6] + assert b == [1, 2, 3, 4, 5, 6] + def test_slice(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[1:5] - assert b == [2,3,4,5] + a = pm.eval("([1,2,3,4,5,6])") + b = a[1:5] + assert b == [2, 3, 4, 5] + def test_slice_negative_start(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[-3:5] - assert b == [4,5] + a = pm.eval("([1,2,3,4,5,6])") + b = a[-3:5] + assert b == [4, 5] + def test_slice_negative_end(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[2:-1] - assert b == [3,4,5] + a = pm.eval("([1,2,3,4,5,6])") + b = a[2:-1] + assert b == [3, 4, 5] + def test_slice_negative_start_negative_end(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[-3:-1] - assert b == [4,5] + a = pm.eval("([1,2,3,4,5,6])") + b = a[-3:-1] + assert b == [4, 5] + def test_slice_step_zero(): - a = pm.eval('([1,2,3,4])') - try: - a[0:6:0] - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "slice step cannot be zero" + a = pm.eval('([1,2,3,4])') + try: + a[0:6:0] + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "slice step cannot be zero" + def test_slice_step_negative(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[0:5:-1] - assert b == [] + a = pm.eval("([1,2,3,4,5,6])") + b = a[0:5:-1] + assert b == [] + def test_slice_step_one(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[1:5:1] - assert b == [2,3,4,5] + a = pm.eval("([1,2,3,4,5,6])") + b = a[1:5:1] + assert b == [2, 3, 4, 5] + def test_slice_step_two(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[1:5:2] - assert b == [2,4] + a = pm.eval("([1,2,3,4,5,6])") + b = a[1:5:2] + assert b == [2, 4] + def test_slice_step_three(): - a = pm.eval("([1,2,3,4,5,6])") - b = a[1:5:3] - assert b == [2,5] + a = pm.eval("([1,2,3,4,5,6])") + b = a[1:5:3] + assert b == [2, 5] + +# slice subscript assign + -#slice subscript assign def test_slice_assign_partial_array(): - a = pm.eval("([1,2,3,4,5,6])") - a[2:4] = [7,8,9,0,1,2] - assert a == [1,2,7,8,9,0,1,2,5,6] + a = pm.eval("([1,2,3,4,5,6])") + a[2:4] = [7, 8, 9, 0, 1, 2] + assert a == [1, 2, 7, 8, 9, 0, 1, 2, 5, 6] + def test_slice_delete_partial_array(): - a = pm.eval("([1,2,3,4,5,6])") - del a[2:4] - assert a == [1,2,5,6] + a = pm.eval("([1,2,3,4,5,6])") + del a[2:4] + assert a == [1, 2, 5, 6] + def test_slice_assign_own_array(): - a = pm.eval("([1,2,3,4,5,6])") - a[2:4] = a - assert a == [1,2,1,2,3,4,5,6,5,6] + a = pm.eval("([1,2,3,4,5,6])") + a[2:4] = a + assert a == [1, 2, 1, 2, 3, 4, 5, 6, 5, 6] + def test_slice_assign_pm_array(): - a = pm.eval("([1,2,3,4,5,6])") - b = pm.eval("([7,8])") - a[2:4] = b - assert a == [1,2,7,8,5,6] + a = pm.eval("([1,2,3,4,5,6])") + b = pm.eval("([7,8])") + a[2:4] = b + assert a == [1, 2, 7, 8, 5, 6] + def test_slice_assign_wrong_type(): - a = pm.eval('([1,2,3,4])') - try: - a[2:4] = 6 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "can only assign an iterable" + a = pm.eval('([1,2,3,4])') + try: + a[2:4] = 6 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "can only assign an iterable" + def test_slice_assign_negative_low(): - a = pm.eval("([1,2,3,4,5,6])") - a[-3:6] = [7,8] - assert a == [1,2,3,7,8] + a = pm.eval("([1,2,3,4,5,6])") + a[-3:6] = [7, 8] + assert a == [1, 2, 3, 7, 8] + def test_slice_assign_negative_low_negative_high(): - a = pm.eval("([1,2,3,4,5,6])") - a[-3:-1] = [7,8] - assert a == [1,2,3,7,8,6] + a = pm.eval("([1,2,3,4,5,6])") + a[-3:-1] = [7, 8] + assert a == [1, 2, 3, 7, 8, 6] + def test_slice_assign_high_larger_than_length(): - a = pm.eval("([1,2,3,4,5,6])") - a[1:8] = [7,8] - assert a == [1,7,8] + a = pm.eval("([1,2,3,4,5,6])") + a[1:8] = [7, 8] + assert a == [1, 7, 8] + def test_slice_assign_clear(): - a = pm.eval("([1,2,3,4,5,6,7,8,9,1,2])") - a[0:32] = [] - assert a == [] + a = pm.eval("([1,2,3,4,5,6,7,8,9,1,2])") + a[0:32] = [] + assert a == [] + def test_slice_delete_partial_array_step_negative(): - a = pm.eval("([1,2,3,4,5,6])") - del a[0:4:-1] - assert a == [1,2,3,4,5,6] + a = pm.eval("([1,2,3,4,5,6])") + del a[0:4:-1] + assert a == [1, 2, 3, 4, 5, 6] + def test_slice_delete_partial_array_step_one(): - a = pm.eval("([1,2,3,4,5,6])") - del a[2:4:1] - assert a == [1,2,5,6] + a = pm.eval("([1,2,3,4,5,6])") + del a[2:4:1] + assert a == [1, 2, 5, 6] + def test_slice_delete_partial_array_step_two(): - a = pm.eval("([1,2,3,4,5,6])") - del a[2:6:2] - assert a == [1,2,4,6] + a = pm.eval("([1,2,3,4,5,6])") + del a[2:6:2] + assert a == [1, 2, 4, 6] + def test_slice_delete_partial_array_step_three(): - a = pm.eval("([1,2,3,4,5,6])") - del a[0:6:3] - assert a == [2,3,5,6] + a = pm.eval("([1,2,3,4,5,6])") + del a[0:6:3] + assert a == [2, 3, 5, 6] + def test_slice_delete_partial_array_step_two_negative_start(): - a = pm.eval("([1,2,3,4,5,6])") - del a[-5:6:2] - assert a == [1,3,5] + a = pm.eval("([1,2,3,4,5,6])") + del a[-5:6:2] + assert a == [1, 3, 5] + def test_slice_delete_partial_array_step_two_negative_start_negative_end(): - a = pm.eval("([1,2,3,4,5,6])") - del a[-5:-2:2] - assert a == [1,3,5,6] + a = pm.eval("([1,2,3,4,5,6])") + del a[-5:-2:2] + assert a == [1, 3, 5, 6] + def test_slice_assign_step_wrong_list_size(): - a = pm.eval('([1,2,3,4])') - try: - a[0:4:2] = [1] - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "attempt to assign sequence of size 1 to extended slice of size 2" + a = pm.eval('([1,2,3,4])') + try: + a[0:4:2] = [1] + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "attempt to assign sequence of size 1 to extended slice of size 2" + def test_slice_assign_partial_array_step_negative(): - a = pm.eval("([1,2,3,4,5,6])") - a[2:4:2] = [7] - assert a == [1,2,7,4,5,6] + a = pm.eval("([1,2,3,4,5,6])") + a[2:4:2] = [7] + assert a == [1, 2, 7, 4, 5, 6] + def test_slice_assign_step_zero(): - a = pm.eval('([1,2,3,4])') - try: - a[0:4:0] = [1] - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "slice step cannot be zero" + a = pm.eval('([1,2,3,4])') + try: + a[0:4:0] = [1] + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "slice step cannot be zero" + def test_slice_assign_partial_array_step_2(): - a = pm.eval("([1,2,3,4,5,6])") - a[2:4:2] = [7] - assert a == [1,2,7,4,5,6] + a = pm.eval("([1,2,3,4,5,6])") + a[2:4:2] = [7] + assert a == [1, 2, 7, 4, 5, 6] + def test_slice_assign_step_wrong_type(): - a = pm.eval('([1,2,3,4])') - try: - a[2:4:2] = 6 - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "must assign iterable to extended slice" + a = pm.eval('([1,2,3,4])') + try: + a[2:4:2] = 6 + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "must assign iterable to extended slice" + def test_slice_assign_partial_array_negative_start(): - a = pm.eval("([1,2,3,4,5,6])") - a[-5:4:2] = [7,8] - assert a == [1, 7, 3, 8, 5, 6] + a = pm.eval("([1,2,3,4,5,6])") + a[-5:4:2] = [7, 8] + assert a == [1, 7, 3, 8, 5, 6] + def test_slice_assign_partial_array_negative_start_negative_stop(): - a = pm.eval("([1,2,3,4,5,6])") - a[-5:-1:2] = [7,8] - assert a == [1, 7, 3, 8, 5, 6] + a = pm.eval("([1,2,3,4,5,6])") + a[-5:-1:2] = [7, 8] + assert a == [1, 7, 3, 8, 5, 6] + def test_slice_assign_own_array_no_match(): - a = pm.eval("([1,2,3,4,5,6])") - try: - a[0:4:2] = a - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e) == "attempt to assign sequence of size 0 to extended slice of size 2" + a = pm.eval("([1,2,3,4,5,6])") + try: + a[0:4:2] = a + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "attempt to assign sequence of size 0 to extended slice of size 2" + def test_slice_assign_pm_array_step_2(): - a = pm.eval("([1,2,3,4,5,6])") - b = pm.eval("([1,2,3])") - a[0:10:2] = b - assert a == [1, 2, 2, 4, 3, 6] - -#__class__ -def test___class__attribute(): - items = pm.eval("([1,2,3,4,5,6])") - assert repr(items.__class__) == "" \ No newline at end of file + a = pm.eval("([1,2,3,4,5,6])") + b = pm.eval("([1,2,3])") + a[0:10:2] = b + assert a == [1, 2, 2, 4, 3, 6] + +# __class__ + + +def test___class__attribute(): + items = pm.eval("([1,2,3,4,5,6])") + assert repr(items.__class__) == "" diff --git a/tests/python/test_objects.py b/tests/python/test_objects.py index 8256c243..a56ba529 100644 --- a/tests/python/test_objects.py +++ b/tests/python/test_objects.py @@ -1,5 +1,6 @@ import pythonmonkey as pm + def test_eval_pyobjects(): class MyClass: pass @@ -8,6 +9,7 @@ class MyClass: proxy_o = pm.eval("(obj) => { return obj; }")(o) assert o is proxy_o + def test_eval_pyobjects_subobjects(): class InnerClass: def __init__(self): @@ -17,120 +19,132 @@ class OuterClass: def __init__(self): self.a = 1 self.b = InnerClass() - + o = OuterClass() assert pm.eval("(obj) => { return obj.a; }")(o) == 1.0 assert pm.eval("(obj) => { return obj.b; }")(o) is o.b assert pm.eval("(obj) => { return obj.b.c; }")(o) == 2.0 + def test_eval_pyobjects_cycle(): class MyClass: def __init__(self): self.a = 1 self.b = 2 self.recursive = self - + o = MyClass() - + assert pm.eval("(obj) => { return obj.a; }")(o) == 1.0 assert pm.eval("(obj) => { return obj.b; }")(o) == 2.0 assert pm.eval("(obj) => { return obj.recursive; }")(o) is o.recursive + def test_eval_pyobjects_proxy_get(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert pm.eval("(obj) => { return obj.a}")(o) == 42.0 + def test_eval_pyobjects_proxy_set(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() pm.eval("(obj) => { obj.b = 43; }")(o) assert o.b == 43.0 + def test_eval_pyobjects_proxy_keys(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert pm.eval("(obj) => { return Object.keys(obj)[0]; }")(o) == 'a' + def test_eval_pyobjects_proxy_delete(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() pm.eval("(obj) => { delete obj.a; }")(o) assert not hasattr(o, 'a') + def test_eval_pyobjects_proxy_has(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert pm.eval("(obj) => { return 'a' in obj; }")(o) + def test_eval_pyobjects_proxy_not_extensible(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert not pm.eval("(o) => Object.isExtensible(o)")(o) assert pm.eval("(o) => Object.preventExtensions(o) === o")(o) + def test_instanceof_pyobject(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert pm.eval("(obj) => { return obj instanceof Object; }")(o) + def test_pyobjects_not_instanceof_string(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert not pm.eval("(obj) => { return obj instanceof String; }")(o) + def test_pyobjects_valueOf(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert o is pm.eval("(obj) => { return obj.valueOf(); }")(o) + def test_pyobjects_toString(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() assert '[object Object]' == pm.eval("(obj) => { return obj.toString(); }")(o) + def test_pyobjects_toLocaleString(): class MyClass: def __init__(self): self.a = 42 - + o = MyClass() - assert '[object Object]' == pm.eval("(obj) => { return obj.toLocaleString(); }")(o) \ No newline at end of file + assert '[object Object]' == pm.eval("(obj) => { return obj.toLocaleString(); }")(o) diff --git a/tests/python/test_pythonmonkey_eval.py b/tests/python/test_pythonmonkey_eval.py index d1be04db..e21cdbe2 100644 --- a/tests/python/test_pythonmonkey_eval.py +++ b/tests/python/test_pythonmonkey_eval.py @@ -6,347 +6,385 @@ from io import StringIO import sys + def test_passes(): - assert True + assert True + def test_eval_numbers_floats(): - for _ in range(10): - py_number = random.uniform(-1000000,1000000) - js_number = pm.eval(repr(py_number)) - assert py_number == js_number + for _ in range(10): + py_number = random.uniform(-1000000, 1000000) + js_number = pm.eval(repr(py_number)) + assert py_number == js_number + def test_eval_numbers_floats_nan(): - jsNaN = pm.eval("NaN") - assert math.isnan(jsNaN) + jsNaN = pm.eval("NaN") + assert math.isnan(jsNaN) + def test_eval_numbers_floats_negative_zero(): - jsNegZero = pm.eval("-0") - assert jsNegZero == 0 - assert jsNegZero == 0.0 # expected that -0.0 == 0.0 == 0 - # https://docs.python.org/3/library/math.html#math.copysign - assert math.copysign(1.0, jsNegZero) == -1.0 + jsNegZero = pm.eval("-0") + assert jsNegZero == 0 + assert jsNegZero == 0.0 # expected that -0.0 == 0.0 == 0 + # https://docs.python.org/3/library/math.html#math.copysign + assert math.copysign(1.0, jsNegZero) == -1.0 + def test_eval_numbers_floats_inf(): - jsPosInf = pm.eval("Infinity") - jsNegInf = pm.eval("-Infinity") - assert jsPosInf == float("+inf") - assert jsNegInf == float("-inf") + jsPosInf = pm.eval("Infinity") + jsNegInf = pm.eval("-Infinity") + assert jsPosInf == float("+inf") + assert jsNegInf == float("-inf") + def test_eval_numbers_integers(): - for _ in range(10): - py_number = random.randint(-1000000,1000000) - js_number = pm.eval(repr(py_number)) - assert py_number == js_number + for _ in range(10): + py_number = random.randint(-1000000, 1000000) + js_number = pm.eval(repr(py_number)) + assert py_number == js_number + def test_eval_booleans(): - py_bool = True - js_bool = pm.eval('true') - assert py_bool == js_bool - py_bool = False - js_bool = pm.eval('false') - assert py_bool == js_bool + py_bool = True + js_bool = pm.eval('true') + assert py_bool == js_bool + py_bool = False + js_bool = pm.eval('false') + assert py_bool == js_bool + def test_eval_dates(): - MIN_YEAR = 1 # https://docs.python.org/3/library/datetime.html#datetime.MINYEAR - MAX_YEAR = 2023 - start = datetime(MIN_YEAR, 1, 1, 00, 00, 00, tzinfo=timezone.utc) - years = MAX_YEAR - MIN_YEAR + 1 - end = start + timedelta(days=365 * years) - for _ in range(10): - py_date = start + (end - start) * random.random() - # round to milliseconds precision because the smallest unit for js Date is 1ms - py_date = py_date.replace(microsecond=min(round(py_date.microsecond, -3), 999000)) # microsecond must be in 0..999999, but it would be rounded to 1000000 if >= 999500 - js_date = pm.eval(f'new Date("{py_date.isoformat()}")') - assert py_date == js_date + MIN_YEAR = 1 # https://docs.python.org/3/library/datetime.html#datetime.MINYEAR + MAX_YEAR = 2023 + start = datetime(MIN_YEAR, 1, 1, 00, 00, 00, tzinfo=timezone.utc) + years = MAX_YEAR - MIN_YEAR + 1 + end = start + timedelta(days=365 * years) + for _ in range(10): + py_date = start + (end - start) * random.random() + # round to milliseconds precision because the smallest unit for js Date is 1ms + # microsecond must be in 0..999999, but it would be rounded to 1000000 if >= 999500 + py_date = py_date.replace(microsecond=min(round(py_date.microsecond, -3), 999000)) + js_date = pm.eval(f'new Date("{py_date.isoformat()}")') + assert py_date == js_date + def test_eval_boxed_booleans(): - py_bool = True - js_bool = pm.eval('new Boolean(true)') - assert py_bool == js_bool - py_bool = False - js_bool = pm.eval('new Boolean(false)') - assert py_bool == js_bool + py_bool = True + js_bool = pm.eval('new Boolean(true)') + assert py_bool == js_bool + py_bool = False + js_bool = pm.eval('new Boolean(false)') + assert py_bool == js_bool + def test_eval_boxed_numbers_floats(): - for _ in range(10): - py_number = random.uniform(-1000000,1000000) - js_number = pm.eval(f'new Number({repr(py_number)})') - assert py_number == js_number + for _ in range(10): + py_number = random.uniform(-1000000, 1000000) + js_number = pm.eval(f'new Number({repr(py_number)})') + assert py_number == js_number + def test_eval_boxed_numbers_integers(): - for _ in range(10): - py_number = random.randint(-1000000,1000000) - js_number = pm.eval(f'new Number({repr(py_number)})') - assert py_number == js_number + for _ in range(10): + py_number = random.randint(-1000000, 1000000) + js_number = pm.eval(f'new Number({repr(py_number)})') + assert py_number == js_number + def test_eval_exceptions(): - # should print out the correct error messages - with pytest.raises(pm.SpiderMonkeyError, match='SyntaxError: "" literal not terminated before end of script'): - pm.eval('"123') - with pytest.raises(pm.SpiderMonkeyError, match="SyntaxError: missing } in compound statement"): - pm.eval('{') - with pytest.raises(pm.SpiderMonkeyError, match="TypeError: can't convert BigInt to number"): - pm.eval('1n + 1') - with pytest.raises(pm.SpiderMonkeyError, match="ReferenceError: RANDOM_VARIABLE is not defined"): - pm.eval('RANDOM_VARIABLE') - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: invalid array length"): - pm.eval('new Array(-1)') - with pytest.raises(pm.SpiderMonkeyError, match="Error: abc"): - # manually by the `throw` statement - pm.eval('throw new Error("abc")') - - # ANYTHING can be thrown in JS - with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: 9007199254740993"): - pm.eval('throw 9007199254740993n') # 2**53+1 - with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: null"): - pm.eval('throw null') - with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: undefined"): - pm.eval('throw undefined') - with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: something from toString"): - # (side effect) calls the `toString` method if an object is thrown - pm.eval('throw { toString() { return "something from toString" } }') - - # convert JS Error object to a Python Exception object for later use (in a `raise` statement) - js_err = pm.eval("new RangeError('to be raised in Python')") - assert isinstance(js_err, BaseException) - assert isinstance(js_err, Exception) - assert type(js_err) == pm.SpiderMonkeyError - with pytest.raises(pm.SpiderMonkeyError, match="RangeError: to be raised in Python"): - raise js_err - - # convert Python Exception object to a JS Error object - get_err_msg = pm.eval("(err) => err.message") - assert "Python BufferError: ttt" == get_err_msg(BufferError("ttt")) - js_rethrow = pm.eval("(err) => { throw err }") - with pytest.raises(pm.SpiderMonkeyError, match="Error: Python BaseException: 123"): - js_rethrow(BaseException("123")) + # should print out the correct error messages + with pytest.raises(pm.SpiderMonkeyError, match='SyntaxError: "" literal not terminated before end of script'): + pm.eval('"123') + with pytest.raises(pm.SpiderMonkeyError, match="SyntaxError: missing } in compound statement"): + pm.eval('{') + with pytest.raises(pm.SpiderMonkeyError, match="TypeError: can't convert BigInt to number"): + pm.eval('1n + 1') + with pytest.raises(pm.SpiderMonkeyError, match="ReferenceError: RANDOM_VARIABLE is not defined"): + pm.eval('RANDOM_VARIABLE') + with pytest.raises(pm.SpiderMonkeyError, match="RangeError: invalid array length"): + pm.eval('new Array(-1)') + with pytest.raises(pm.SpiderMonkeyError, match="Error: abc"): + # manually by the `throw` statement + pm.eval('throw new Error("abc")') + + # ANYTHING can be thrown in JS + with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: 9007199254740993"): + pm.eval('throw 9007199254740993n') # 2**53+1 + with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: null"): + pm.eval('throw null') + with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: undefined"): + pm.eval('throw undefined') + with pytest.raises(pm.SpiderMonkeyError, match="uncaught exception: something from toString"): + # (side effect) calls the `toString` method if an object is thrown + pm.eval('throw { toString() { return "something from toString" } }') + + # convert JS Error object to a Python Exception object for later use (in a `raise` statement) + js_err = pm.eval("new RangeError('to be raised in Python')") + assert isinstance(js_err, BaseException) + assert isinstance(js_err, Exception) + assert type(js_err) is pm.SpiderMonkeyError + with pytest.raises(pm.SpiderMonkeyError, match="RangeError: to be raised in Python"): + raise js_err + + # convert Python Exception object to a JS Error object + get_err_msg = pm.eval("(err) => err.message") + assert "Python BufferError: ttt" == get_err_msg(BufferError("ttt")) + js_rethrow = pm.eval("(err) => { throw err }") + with pytest.raises(pm.SpiderMonkeyError, match="Error: Python BaseException: 123"): + js_rethrow(BaseException("123")) + def test_eval_exceptions_nested_py_js_py(): - def c(): - raise Exception('this is an exception') - b = pm.eval('''(x) => { - try { - x() + def c(): + raise Exception('this is an exception') + b = pm.eval('''(x) => { + try { + x() } catch(e) { return "Caught in JS " + e; } }''') - assert str(b(c)).__contains__("Caught in JS Error: Python Exception: this is an exception") - assert str(b(c)).__contains__("test_pythonmonkey_eval.py") - assert str(b(c)).__contains__("line 126") - assert str(b(c)).__contains__("in c") + assert str(b(c)).__contains__("Caught in JS Error: Python Exception: this is an exception") + assert str(b(c)).__contains__("test_pythonmonkey_eval.py") + assert str(b(c)).__contains__("line 140") + assert str(b(c)).__contains__("in c") + def test_eval_exceptions_nested_js_py_js(): - c = pm.eval("() => { throw TypeError('this is an exception'); }") + c = pm.eval("() => { throw TypeError('this is an exception'); }") + + def b(x): + try: + x() + return "" + except Exception as e: + return "Caught in Py " + str(e) + ret = b(c) + assert ("Caught in Py Error in" in ret) and ("TypeError: this is an exception" in ret) - def b(x): - try: - x() - return "" - except Exception as e: - return "Caught in Py " + str(e) - ret = b(c) - assert ("Caught in Py Error in" in ret) and ("TypeError: this is an exception" in ret) def test_eval_undefined(): - x = pm.eval("undefined") - assert x == None + x = pm.eval("undefined") + assert x is None + def test_eval_null(): - x = pm.eval("null") - assert x == pm.null - + x = pm.eval("null") + assert x == pm.null + + def test_eval_functions(): - f = pm.eval("() => { return undefined }") - assert f() == None - - g = pm.eval("() => { return null}") - assert g() == pm.null - - h = pm.eval("(a, b) => {return a + b}") - n = 10 - for _ in range(n): - a = random.randint(-1000, 1000) - b = random.randint(-1000, 1000) - assert h(a, b) == (a + b) - - for _ in range (n): - a = random.uniform(-1000.0, 1000.0) - b = random.uniform(-1000.0, 1000.0) - assert h(a, b) == (a + b) - - assert math.isnan(h(float("nan"), 1)) - assert math.isnan(h(float("+inf"), float("-inf"))) + f = pm.eval("() => { return undefined }") + assert f() is None + + g = pm.eval("() => { return null}") + assert g() == pm.null + + h = pm.eval("(a, b) => {return a + b}") + n = 10 + for _ in range(n): + a = random.randint(-1000, 1000) + b = random.randint(-1000, 1000) + assert h(a, b) == (a + b) + + for _ in range(n): + a = random.uniform(-1000.0, 1000.0) + b = random.uniform(-1000.0, 1000.0) + assert h(a, b) == (a + b) + + assert math.isnan(h(float("nan"), 1)) + assert math.isnan(h(float("+inf"), float("-inf"))) + def test_eval_functions_latin1_string_args(): - concatenate = pm.eval("(a, b) => { return a + b}") - n = 10 - for i in range(n): - length1 = random.randint(0x0000, 0xFFFF) - length2 = random.randint(0x0000, 0xFFFF) - string1 = '' - string2 = '' - - for j in range(length1): - codepoint = random.randint(0x00, 0xFFFF) - string1 += chr(codepoint) # add random chr in ucs2 range - for j in range(length2): - codepoint = random.randint(0x00, 0xFFFF) - string2 += chr(codepoint) - - assert concatenate(string1, string2) == (string1 + string2) + concatenate = pm.eval("(a, b) => { return a + b}") + n = 10 + for i in range(n): + length1 = random.randint(0x0000, 0xFFFF) + length2 = random.randint(0x0000, 0xFFFF) + string1 = '' + string2 = '' + + for j in range(length1): + codepoint = random.randint(0x00, 0xFFFF) + string1 += chr(codepoint) # add random chr in ucs2 range + for j in range(length2): + codepoint = random.randint(0x00, 0xFFFF) + string2 += chr(codepoint) + + assert concatenate(string1, string2) == (string1 + string2) + def test_eval_functions_ucs2_string_args(): - concatenate = pm.eval("(a, b) => { return a + b}") - n = 10 - for i in range(n): - length1 = random.randint(0x0000, 0xFFFF) - length2 = random.randint(0x0000, 0xFFFF) - string1 = '' - string2 = '' - - for j in range(length1): - codepoint = random.randint(0x00, 0xFF) - string1 += chr(codepoint) # add random chr in latin1 range - for j in range(length2): - codepoint = random.randint(0x00, 0xFF) - string2 += chr(codepoint) - - assert concatenate(string1, string2) == (string1 + string2) + concatenate = pm.eval("(a, b) => { return a + b}") + n = 10 + for i in range(n): + length1 = random.randint(0x0000, 0xFFFF) + length2 = random.randint(0x0000, 0xFFFF) + string1 = '' + string2 = '' + + for j in range(length1): + codepoint = random.randint(0x00, 0xFF) + string1 += chr(codepoint) # add random chr in latin1 range + for j in range(length2): + codepoint = random.randint(0x00, 0xFF) + string2 += chr(codepoint) + + assert concatenate(string1, string2) == (string1 + string2) + def test_eval_functions_ucs4_string_args(): - concatenate = pm.eval("(a, b) => { return a + b}") - n = 10 - for i in range(n): - length1 = random.randint(0x0000, 0xFFFF) - length2 = random.randint(0x0000, 0xFFFF) - string1 = '' - string2 = '' - - for j in range(length1): - codepoint = random.randint(0x010000, 0x10FFFF) - string1 += chr(codepoint) # add random chr outside BMP - for j in range(length2): - codepoint = random.randint(0x010000, 0x10FFFF) - string2 += chr(codepoint) - - assert concatenate(string1, string2) == (string1 + string2) + concatenate = pm.eval("(a, b) => { return a + b}") + n = 10 + for i in range(n): + length1 = random.randint(0x0000, 0xFFFF) + length2 = random.randint(0x0000, 0xFFFF) + string1 = '' + string2 = '' + + for j in range(length1): + codepoint = random.randint(0x010000, 0x10FFFF) + string1 += chr(codepoint) # add random chr outside BMP + for j in range(length2): + codepoint = random.randint(0x010000, 0x10FFFF) + string2 += chr(codepoint) + + assert concatenate(string1, string2) == (string1 + string2) + def test_eval_functions_roundtrip(): - # BF-60 https://github.com/Distributive-Network/PythonMonkey/pull/18 - def ident(x): - return x - js_fn_back = pm.eval("(py_fn) => py_fn(()=>{ return 'YYZ' })")(ident) - # pm.collect() # TODO: to be fixed in BF-59 - assert "YYZ" == js_fn_back() + # BF-60 https://github.com/Distributive-Network/PythonMonkey/pull/18 + def ident(x): + return x + js_fn_back = pm.eval("(py_fn) => py_fn(()=>{ return 'YYZ' })")(ident) + # pm.collect() # TODO: to be fixed in BF-59 + assert "YYZ" == js_fn_back() + def test_eval_functions_pyfunction_in_closure(): - # BF-58 https://github.com/Distributive-Network/PythonMonkey/pull/19 - def fn1(): - def fn0(n): - return n + 100 - return fn0 - assert 101.9 == fn1()(1.9) - assert 101.9 == pm.eval("(fn1) => { return fn1 }")(fn1())(1.9) - assert 101.9 == pm.eval("(fn1, x) => { return fn1()(x) }")(fn1, 1.9) - assert 101.9 == pm.eval("(fn1) => { return fn1() }")(fn1)(1.9) + # BF-58 https://github.com/Distributive-Network/PythonMonkey/pull/19 + def fn1(): + def fn0(n): + return n + 100 + return fn0 + assert 101.9 == fn1()(1.9) + assert 101.9 == pm.eval("(fn1) => { return fn1 }")(fn1())(1.9) + assert 101.9 == pm.eval("(fn1, x) => { return fn1()(x) }")(fn1, 1.9) + assert 101.9 == pm.eval("(fn1) => { return fn1() }")(fn1)(1.9) + def test_unwrap_py_function(): - # https://github.com/Distributive-Network/PythonMonkey/issues/65 - def pyFunc(): - pass - unwrappedPyFunc = pm.eval("(wrappedPyFunc) => { return wrappedPyFunc }")(pyFunc) - assert unwrappedPyFunc is pyFunc + # https://github.com/Distributive-Network/PythonMonkey/issues/65 + def pyFunc(): + pass + unwrappedPyFunc = pm.eval("(wrappedPyFunc) => { return wrappedPyFunc }")(pyFunc) + assert unwrappedPyFunc is pyFunc + def test_unwrap_js_function(): - # https://github.com/Distributive-Network/PythonMonkey/issues/65 - wrappedJSFunc = pm.eval("const JSFunc = () => { return 0 }\nJSFunc") - assert pm.eval("(unwrappedJSFunc) => { return unwrappedJSFunc === JSFunc }")(wrappedJSFunc) + # https://github.com/Distributive-Network/PythonMonkey/issues/65 + wrappedJSFunc = pm.eval("const JSFunc = () => { return 0 }\nJSFunc") + assert pm.eval("(unwrappedJSFunc) => { return unwrappedJSFunc === JSFunc }")(wrappedJSFunc) + def test_eval_functions_pyfunctions_ints(): - caller = pm.eval("(func, param1, param2) => { return func(param1, param2) }") - def add(a, b): - return a + b - n = 10 - for i in range(n): - int1 = random.randint(0x0000, 0xFFFF) - int2 = random.randint(0x0000, 0xFFFF) - assert caller(add, int1, int2) == int1 + int2 + caller = pm.eval("(func, param1, param2) => { return func(param1, param2) }") + + def add(a, b): + return a + b + n = 10 + for i in range(n): + int1 = random.randint(0x0000, 0xFFFF) + int2 = random.randint(0x0000, 0xFFFF) + assert caller(add, int1, int2) == int1 + int2 + def test_eval_functions_pyfunctions_strs(): - caller = pm.eval("(func, param1, param2) => { return func(param1, param2) }") - def concatenate(a, b): - return a + b - n = 10 - for i in range(n): - length1 = random.randint(0x0000, 0xFFFF) - length2 = random.randint(0x0000, 0xFFFF) - string1 = '' - string2 = '' - - for j in range(length1): - codepoint = random.randint(0x0000, 0xFFFF) - string1 += chr(codepoint) # add random chr - for j in range(length2): - codepoint = random.randint(0x0000, 0xFFFF) - string2 += chr(codepoint) - assert caller(concatenate, string1, string2) == string1 + string2 + caller = pm.eval("(func, param1, param2) => { return func(param1, param2) }") + + def concatenate(a, b): + return a + b + n = 10 + for i in range(n): + length1 = random.randint(0x0000, 0xFFFF) + length2 = random.randint(0x0000, 0xFFFF) + string1 = '' + string2 = '' + + for j in range(length1): + codepoint = random.randint(0x0000, 0xFFFF) + string1 += chr(codepoint) # add random chr + for j in range(length2): + codepoint = random.randint(0x0000, 0xFFFF) + string2 += chr(codepoint) + assert caller(concatenate, string1, string2) == string1 + string2 + def test_py_evaloptions_string_type(): - evalOpts = {'filename': 'GoodFile'} - try: - pm.eval("{throw new Error()}", evalOpts) - except Exception as e: - assert str(e).__contains__("Error in file GoodFile") + evalOpts = {'filename': 'GoodFile'} + try: + pm.eval("{throw new Error()}", evalOpts) + except Exception as e: + assert str(e).__contains__("Error in file GoodFile") + def test_js_evaloptions_string_type(): - evalOpts = pm.eval("({'filename': 'GoodFile'})") - try: - pm.eval("{throw new Error()}", evalOpts) - except Exception as e: - assert str(e).__contains__("Error in file GoodFile") + evalOpts = pm.eval("({'filename': 'GoodFile'})") + try: + pm.eval("{throw new Error()}", evalOpts) + except Exception as e: + assert str(e).__contains__("Error in file GoodFile") + def test_py_evaloptions_long_type(): - evalOpts = {'lineno': 10} - try: - pm.eval("{throw new Error()}", evalOpts) - except Exception as e: - assert str(e).__contains__("on line 10") + evalOpts = {'lineno': 10} + try: + pm.eval("{throw new Error()}", evalOpts) + except Exception as e: + assert str(e).__contains__("on line 10") + def test_js_evaloptions_long_type(): - evalOpts = pm.eval("({'lineno': 10})") - try: - pm.eval("{throw new Error()}", evalOpts) - except Exception as e: - assert str(e).__contains__("on line 10") + evalOpts = pm.eval("({'lineno': 10})") + try: + pm.eval("{throw new Error()}", evalOpts) + except Exception as e: + assert str(e).__contains__("on line 10") + def test_py_evaloptions_boolean_type(): - evalOpts = {'strict': True} - try: - pm.eval("{a = 9}", evalOpts) - except Exception as e: - assert str(e).__contains__("ReferenceError: assignment to undeclared variable a") + evalOpts = {'strict': True} + try: + pm.eval("{a = 9}", evalOpts) + except Exception as e: + assert str(e).__contains__("ReferenceError: assignment to undeclared variable a") + def test_js_evaloptions_boolean_type(): - evalOpts = pm.eval("({'strict': true})") - try: - pm.eval("{a = 9}", evalOpts) - except Exception as e: - assert str(e).__contains__("ReferenceError: assignment to undeclared variable a") + evalOpts = pm.eval("({'strict': true})") + try: + pm.eval("{a = 9}", evalOpts) + except Exception as e: + assert str(e).__contains__("ReferenceError: assignment to undeclared variable a") + def test_globalThis(): - obj = pm.eval('globalThis') - assert str(obj).__contains__("{'python': {'pythonMonkey':") + obj = pm.eval('globalThis') + assert str(obj).__contains__("{'python': {'pythonMonkey':") + def test_console_globalThis(): - temp_out = StringIO() - sys.stdout = temp_out - pm.eval('console.log(globalThis)') - assert temp_out.getvalue().__contains__("{ python: \n { pythonMonkey: \n") + temp_out = StringIO() + sys.stdout = temp_out + pm.eval('console.log(globalThis)') + assert temp_out.getvalue().__contains__("{ python: \n { pythonMonkey: \n") + def test_console_array(): - temp_out = StringIO() - sys.stdout = temp_out - items = [1, 2, 3] - pm.eval('console.log')(items) - assert temp_out.getvalue() == "[ \x1b[33m1\x1b[39m, \x1b[33m2\x1b[39m, \x1b[33m3\x1b[39m ]\n" \ No newline at end of file + temp_out = StringIO() + sys.stdout = temp_out + items = [1, 2, 3] + pm.eval('console.log')(items) + assert temp_out.getvalue() == "[ \x1b[33m1\x1b[39m, \x1b[33m2\x1b[39m, \x1b[33m3\x1b[39m ]\n" diff --git a/tests/python/test_reentrance_smoke.py b/tests/python/test_reentrance_smoke.py index 623c6bd4..e0ce9cdd 100644 --- a/tests/python/test_reentrance_smoke.py +++ b/tests/python/test_reentrance_smoke.py @@ -4,15 +4,16 @@ # @author Wes Garland, wes@distributive.network # @date June 2023 -import sys, os +import sys +import os import pythonmonkey as pm def test_reentrance(): - globalThis = pm.eval("globalThis;"); - globalThis.pmEval = pm.eval; - globalThis.pyEval = eval; + globalThis = pm.eval("globalThis;") + globalThis.pmEval = pm.eval + globalThis.pyEval = eval - abc=(pm.eval("() => { return {def: pyEval('123')} };"))() - assert(abc['def'] == 123) - print(pm.eval("pmEval(`pyEval(\"'test passed'\")`)")) + abc = (pm.eval("() => { return {def: pyEval('123')} };"))() + assert (abc['def'] == 123) + print(pm.eval("pmEval(`pyEval(\"'test passed'\")`)")) diff --git a/tests/python/test_strings.py b/tests/python/test_strings.py index 914782b3..694de79f 100644 --- a/tests/python/test_strings.py +++ b/tests/python/test_strings.py @@ -2,255 +2,266 @@ import gc import random + def test_eval_ascii_string_matches_evaluated_string(): - py_ascii_string = "abc" - js_ascii_string = pm.eval(repr(py_ascii_string)) - assert py_ascii_string == js_ascii_string + py_ascii_string = "abc" + js_ascii_string = pm.eval(repr(py_ascii_string)) + assert py_ascii_string == js_ascii_string + def test_eval_latin1_string_matches_evaluated_string(): - py_latin1_string = "a©Ð" - js_latin1_string = pm.eval(repr(py_latin1_string)) - assert py_latin1_string == js_latin1_string + py_latin1_string = "a©Ð" + js_latin1_string = pm.eval(repr(py_latin1_string)) + assert py_latin1_string == js_latin1_string + def test_eval_null_character_string_matches_evaluated_string(): - py_null_character_string = "a\x00©" - js_null_character_string = pm.eval(repr(py_null_character_string)) - assert py_null_character_string == js_null_character_string + py_null_character_string = "a\x00©" + js_null_character_string = pm.eval(repr(py_null_character_string)) + assert py_null_character_string == js_null_character_string + def test_eval_ucs2_string_matches_evaluated_string(): - py_ucs2_string = "ՄԸՋ" - js_ucs2_string = pm.eval(repr(py_ucs2_string)) - assert py_ucs2_string == js_ucs2_string + py_ucs2_string = "ՄԸՋ" + js_ucs2_string = pm.eval(repr(py_ucs2_string)) + assert py_ucs2_string == js_ucs2_string + def test_eval_unpaired_surrogate_string_matches_evaluated_string(): - py_unpaired_surrogate_string = "Ջ©\ud8fe" - js_unpaired_surrogate_string = pm.eval(repr(py_unpaired_surrogate_string)) - assert py_unpaired_surrogate_string == js_unpaired_surrogate_string + py_unpaired_surrogate_string = "Ջ©\ud8fe" + js_unpaired_surrogate_string = pm.eval(repr(py_unpaired_surrogate_string)) + assert py_unpaired_surrogate_string == js_unpaired_surrogate_string + def test_eval_ucs4_string_matches_evaluated_string(): - py_ucs4_string = "🀄🀛🜢" - js_ucs4_string = pm.eval(repr(py_ucs4_string)) - assert py_ucs4_string == js_ucs4_string + py_ucs4_string = "🀄🀛🜢" + js_ucs4_string = pm.eval(repr(py_ucs4_string)) + assert py_ucs4_string == js_ucs4_string + def test_eval_latin1_string_fuzztest(): - n = 10 - for _ in range(n): - length = random.randint(0x0000, 0xFFFF) - string1 = '' - - for _ in range(length): - codepoint = random.randint(0x00, 0xFF) - string1 += chr(codepoint) # add random chr in latin1 range - - - INITIAL_STRING = string1 - m = 10 - for _ in range(m): - string2 = pm.eval(repr(string1)) - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - gc.collect() - pm.collect() - - #garbage collection should not collect variables still in scope - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - string1 = string2 - assert INITIAL_STRING == string1 #strings should still match after a bunch of iterations through JS + n = 10 + for _ in range(n): + length = random.randint(0x0000, 0xFFFF) + string1 = '' + + for _ in range(length): + codepoint = random.randint(0x00, 0xFF) + string1 += chr(codepoint) # add random chr in latin1 range + + INITIAL_STRING = string1 + m = 10 + for _ in range(m): + string2 = pm.eval(repr(string1)) + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + gc.collect() + pm.collect() + + # garbage collection should not collect variables still in scope + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + string1 = string2 + assert INITIAL_STRING == string1 # strings should still match after a bunch of iterations through JS + def test_eval_ucs2_string_fuzztest(): - n = 10 - for _ in range(n): - length = random.randint(0x0000, 0xFFFF) - string1 = '' - - for _i in range(length): - codepoint = random.randint(0x00, 0xFFFF) - string1 += chr(codepoint) # add random chr in ucs2 range - - - INITIAL_STRING = string1 - m = 10 - for _ in range(m): - string2 = pm.eval(repr(string1)) - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - gc.collect() - pm.collect() - - #garbage collection should not collect variables still in scope - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - string1 = string2 - assert INITIAL_STRING == string1 #strings should still match after a bunch of iterations through JS + n = 10 + for _ in range(n): + length = random.randint(0x0000, 0xFFFF) + string1 = '' + + for _i in range(length): + codepoint = random.randint(0x00, 0xFFFF) + string1 += chr(codepoint) # add random chr in ucs2 range + + INITIAL_STRING = string1 + m = 10 + for _ in range(m): + string2 = pm.eval(repr(string1)) + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + gc.collect() + pm.collect() + + # garbage collection should not collect variables still in scope + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + string1 = string2 + assert INITIAL_STRING == string1 # strings should still match after a bunch of iterations through JS + def test_eval_ucs4_string_fuzztest(): - n = 10 - for _ in range(n): - length = random.randint(0x0000, 0xFFFF) - string1 = '' - - for _ in range(length): - codepoint = random.randint(0x010000, 0x10FFFF) - string1 += chr(codepoint) # add random chr outside BMP - - - INITIAL_STRING = string1 - m = 10 - for _ in range(m): - string2 = pm.eval("'" + string1 + "'") - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - gc.collect() - pm.collect() - - #garbage collection should not collect variables still in scope - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - string1 = string2 - assert INITIAL_STRING == string1 #strings should still match after a bunch of iterations through JS + n = 10 + for _ in range(n): + length = random.randint(0x0000, 0xFFFF) + string1 = '' + + for _ in range(length): + codepoint = random.randint(0x010000, 0x10FFFF) + string1 += chr(codepoint) # add random chr outside BMP + + INITIAL_STRING = string1 + m = 10 + for _ in range(m): + string2 = pm.eval("'" + string1 + "'") + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + gc.collect() + pm.collect() + + # garbage collection should not collect variables still in scope + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + string1 = string2 + assert INITIAL_STRING == string1 # strings should still match after a bunch of iterations through JS def test_eval_boxed_ascii_string_matches_evaluated_string(): - py_ascii_string = "abc" - js_ascii_string = pm.eval(f'new String({repr(py_ascii_string)})') - assert py_ascii_string == js_ascii_string + py_ascii_string = "abc" + js_ascii_string = pm.eval(f'new String({repr(py_ascii_string)})') + assert py_ascii_string == js_ascii_string + def test_eval_boxed_latin1_string_matches_evaluated_string(): - py_latin1_string = "a©Ð" - js_latin1_string = pm.eval(f'new String({repr(py_latin1_string)})') - assert py_latin1_string == js_latin1_string + py_latin1_string = "a©Ð" + js_latin1_string = pm.eval(f'new String({repr(py_latin1_string)})') + assert py_latin1_string == js_latin1_string + def test_eval_boxed_null_character_string_matches_evaluated_string(): - py_null_character_string = "a\x00©" - js_null_character_string = pm.eval(f'new String({repr(py_null_character_string)})') - assert py_null_character_string == js_null_character_string + py_null_character_string = "a\x00©" + js_null_character_string = pm.eval(f'new String({repr(py_null_character_string)})') + assert py_null_character_string == js_null_character_string + def test_eval_boxed_ucs2_string_matches_evaluated_string(): - py_ucs2_string = "ՄԸՋ" - js_ucs2_string = pm.eval(f'new String({repr(py_ucs2_string)})') - assert py_ucs2_string == js_ucs2_string + py_ucs2_string = "ՄԸՋ" + js_ucs2_string = pm.eval(f'new String({repr(py_ucs2_string)})') + assert py_ucs2_string == js_ucs2_string + def test_eval_boxed_unpaired_surrogate_string_matches_evaluated_string(): - py_unpaired_surrogate_string = "Ջ©\ud8fe" - js_unpaired_surrogate_string = pm.eval(f'new String({repr(py_unpaired_surrogate_string)})') - assert py_unpaired_surrogate_string == js_unpaired_surrogate_string + py_unpaired_surrogate_string = "Ջ©\ud8fe" + js_unpaired_surrogate_string = pm.eval(f'new String({repr(py_unpaired_surrogate_string)})') + assert py_unpaired_surrogate_string == js_unpaired_surrogate_string + def test_eval_boxed_ucs4_string_matches_evaluated_string(): - py_ucs4_string = "🀄🀛🜢" - js_ucs4_string = pm.eval(f'new String({repr(py_ucs4_string)})') - assert py_ucs4_string == js_ucs4_string + py_ucs4_string = "🀄🀛🜢" + js_ucs4_string = pm.eval(f'new String({repr(py_ucs4_string)})') + assert py_ucs4_string == js_ucs4_string + def test_eval_boxed_latin1_string_fuzztest(): - n = 10 - for _ in range(n): - length = random.randint(0x0000, 0xFFFF) - string1 = '' - - for _ in range(length): - codepoint = random.randint(0x00, 0xFF) - string1 += chr(codepoint) # add random chr in latin1 range - - - INITIAL_STRING = string1 - m = 10 - for _ in range(m): - string2 = pm.eval(f'new String({repr(string1)})') - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - gc.collect() - pm.collect() - - #garbage collection should not collect variables still in scope - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - string1 = string2 - assert INITIAL_STRING == string1 #strings should still match after a bunch of iterations through JS + n = 10 + for _ in range(n): + length = random.randint(0x0000, 0xFFFF) + string1 = '' + + for _ in range(length): + codepoint = random.randint(0x00, 0xFF) + string1 += chr(codepoint) # add random chr in latin1 range + + INITIAL_STRING = string1 + m = 10 + for _ in range(m): + string2 = pm.eval(f'new String({repr(string1)})') + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + gc.collect() + pm.collect() + + # garbage collection should not collect variables still in scope + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + string1 = string2 + assert INITIAL_STRING == string1 # strings should still match after a bunch of iterations through JS + def test_eval_boxed_ucs2_string_fuzztest(): - n = 10 - for _ in range(n): - length = random.randint(0x0000, 0xFFFF) - string1 = '' - - for _ in range(length): - codepoint = random.randint(0x00, 0xFFFF) - string1 += chr(codepoint) # add random chr in ucs2 range - - - INITIAL_STRING = string1 - m = 10 - for _ in range(m): - string2 = pm.eval(f'new String({repr(string1)})') - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - gc.collect() - pm.collect() - - #garbage collection should not collect variables still in scope - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - string1 = string2 - assert INITIAL_STRING == string1 #strings should still match after a bunch of iterations through JS + n = 10 + for _ in range(n): + length = random.randint(0x0000, 0xFFFF) + string1 = '' + + for _ in range(length): + codepoint = random.randint(0x00, 0xFFFF) + string1 += chr(codepoint) # add random chr in ucs2 range + + INITIAL_STRING = string1 + m = 10 + for _ in range(m): + string2 = pm.eval(f'new String({repr(string1)})') + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + gc.collect() + pm.collect() + + # garbage collection should not collect variables still in scope + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + string1 = string2 + assert INITIAL_STRING == string1 # strings should still match after a bunch of iterations through JS + def test_eval_boxed_ucs4_string_fuzztest(): - n = 10 - for _ in range(n): - length = random.randint(0x0000, 0xFFFF) - string1 = '' - - for _ in range(length): - codepoint = random.randint(0x010000, 0x10FFFF) - string1 += chr(codepoint) # add random chr outside BMP - - - INITIAL_STRING = string1 - m = 10 - for _ in range(m): - string2 = pm.eval(f'new String("{string1}")') - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - gc.collect() - pm.collect() - - #garbage collection should not collect variables still in scope - assert len(string1) == length - assert len(string2) == length - assert len(string1) == len(string2) - assert string1 == string2 - - string1 = string2 - assert INITIAL_STRING == string1 #strings should still match after a bunch of iterations through JS + n = 10 + for _ in range(n): + length = random.randint(0x0000, 0xFFFF) + string1 = '' + + for _ in range(length): + codepoint = random.randint(0x010000, 0x10FFFF) + string1 += chr(codepoint) # add random chr outside BMP + + INITIAL_STRING = string1 + m = 10 + for _ in range(m): + string2 = pm.eval(f'new String("{string1}")') + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + gc.collect() + pm.collect() + + # garbage collection should not collect variables still in scope + assert len(string1) == length + assert len(string2) == length + assert len(string1) == len(string2) + assert string1 == string2 + + string1 = string2 + assert INITIAL_STRING == string1 # strings should still match after a bunch of iterations through JS diff --git a/cmake/format/uncrustify.cfg b/uncrustify.cfg similarity index 99% rename from cmake/format/uncrustify.cfg rename to uncrustify.cfg index 56d94610..0c7b4793 100644 --- a/cmake/format/uncrustify.cfg +++ b/uncrustify.cfg @@ -29,7 +29,7 @@ indent_namespace = true indent_off_after_assign = true indent_paren_open_brace = true indent_paren_close = indent_columns -indent_shift = true +indent_shift = 1 indent_template_param = true indent_var_def_cont = true indent_with_tabs = 0 @@ -85,7 +85,7 @@ sp_before_semi_for_empty = remove sp_before_sparen = force sp_before_square = remove sp_before_squares = remove -sp_before_tr_emb_cmt = add +sp_before_tr_cmt = add sp_before_type_brace_init_lst_close = remove sp_between_new_paren = remove sp_between_ptr_star = remove