diff --git a/.gitignore b/.gitignore index 46f53fb51..6517d250c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ docs/doxygen # Tests tests/test262/ +tests/unit-doc/*.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d27997cb6..495b8a286 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ set(JERRY_EXT ON CACHE BOOL "Build jerry-ext?") set(JERRY_LIBC ON CACHE BOOL "Build and use jerry-libc?") set(JERRY_LIBM ON CACHE BOOL "Build and use jerry-libm?") set(UNITTESTS OFF CACHE BOOL "Build unit tests?") +set(DOCTESTS OFF CACHE BOOL "Build doc tests?") # Optional build settings set(ENABLE_ALL_IN_ONE OFF CACHE BOOL "Enable all-in-one build?") @@ -53,16 +54,16 @@ set(ENABLE_STATIC_LINK ON CACHE BOOL "Enable static linking?") set(ENABLE_STRIP ON CACHE BOOL "Enable stripping all symbols from release binary?") # Option overrides -if(JERRY_CMDLINE OR JERRY_CMDLINE_MINIMAL) +if(JERRY_CMDLINE OR JERRY_CMDLINE_MINIMAL OR UNITTESTS OR DOCTESTS) set(JERRY_PORT_DEFAULT ON) - set(JERRY_PORT_DEFAULT_MESSAGE " (FORCED BY CMDLINE)") + set(JERRY_PORT_DEFAULT_MESSAGE " (FORCED BY CMDLINE OR TESTS)") endif() -if(JERRY_CMDLINE) +if(JERRY_CMDLINE OR DOCTESTS) set(JERRY_EXT ON) - set(JERRY_EXT_MESSAGE " (FORCED BY CMDLINE)") + set(JERRY_EXT_MESSAGE " (FORCED BY CMDLINE OR TESTS)") endif() if("${PLATFORM}" STREQUAL "DARWIN") @@ -104,6 +105,7 @@ message(STATUS "JERRY_EXT " ${JERRY_EXT} ${JERRY_EXT_MESSAGE}) message(STATUS "JERRY_LIBC " ${JERRY_LIBC} ${JERRY_LIBC_MESSAGE}) message(STATUS "JERRY_LIBM " ${JERRY_LIBM} ${JERRY_LIBM_MESSAGE}) message(STATUS "UNITTESTS " ${UNITTESTS}) +message(STATUS "DOCTESTS " ${DOCTESTS}) # Setup directories # Project binary dir @@ -180,6 +182,8 @@ if (USING_GCC OR USING_CLANG) endif() if(("${PLATFORM}" STREQUAL "DARWIN")) jerry_add_link_flags(-lSystem) + set(CMAKE_C_ARCHIVE_CREATE " Sqc ") + set(CMAKE_C_ARCHIVE_FINISH " -no_warning_for_no_symbols -c ") else() jerry_add_link_flags(-Wl,-z,noexecstack) endif() @@ -274,3 +278,8 @@ if(UNITTESTS) add_subdirectory(tests/unit-ext) endif() endif() + +# Doctests +if(DOCTESTS) + add_subdirectory(tests/unit-doc) +endif() diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index 5edc21075..e94d2aa05 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -303,7 +303,13 @@ jerry_init (jerry_init_flag_t flags) **Example** +[doctest]: # () + ```c +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_SHOW_OPCODES | JERRY_INIT_SHOW_REGEXP_OPCODES); @@ -363,7 +369,11 @@ jerry_get_context_data (const jerry_context_data_manager *manager_p); **Example** +[doctest]: # (test="compile") + ```c +#include "jerryscript.h" + typedef struct { int my_data1; @@ -404,7 +414,8 @@ static const jerry_context_data_manager_t my_manager = * Then, in some function in your code, you can retrieve an item of type my_context_data_t from the currently active * context such that JerryScript will create and store such an item if one was not previously created */ -void someplace_in_the_code (void) +static void +someplace_in_the_code (void) { my_context_data_t *my_data = (my_context_data_t *) jerry_get_context_data (&my_manager); /* Perform useful things using the data found in my_data */ @@ -435,7 +446,13 @@ jerry_register_magic_strings (const jerry_char_ptr_t *ex_str_items_p, **Example** +[doctest]: # () + ```c +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_EMPTY); @@ -450,9 +467,9 @@ jerry_register_magic_strings (const jerry_char_ptr_t *ex_str_items_p, // must be static, because 'jerry_register_magic_strings' does not copy static const jerry_length_t magic_string_lengths[] = { - (jerry_length_t)strlen (magic_string_items[0]), - (jerry_length_t)strlen (magic_string_items[1]), - (jerry_length_t)strlen (magic_string_items[2]) + 12, + 12, + 12 }; jerry_register_magic_strings (magic_string_items, num_magic_string_items, magic_string_lengths); } @@ -484,7 +501,13 @@ jerry_get_memory_limits (size_t *out_data_bss_brk_limit_p, **Example** +[doctest]: # () + ```c +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_EMPTY); @@ -552,9 +575,16 @@ jerry_run_simple (const jerry_char_t *script_source_p, **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { - const jerry_char_t *script = "print ('Hello, World!');"; + const jerry_char_t *script = (const jerry_char_t *) "print ('Hello, World!');"; jerry_run_simple (script, strlen ((const char *) script), JERRY_INIT_EMPTY); } @@ -596,7 +626,14 @@ jerry_parse (const jerry_char_t *source_p, **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_EMPTY); @@ -671,7 +708,14 @@ jerry_run (const jerry_value_t func_val); **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { const jerry_char_t script[] = "print ('Hello, World!');"; size_t script_size = strlen ((const char *) script); @@ -756,7 +800,14 @@ jerry_run_all_enqueued_jobs (void) **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_EMPTY); @@ -3829,7 +3880,14 @@ jerry_is_valid_utf8_string (const jerry_char_t *utf8_buf_p, /**< UTF-8 string */ **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { const jerry_char_t script[] = "print ('Hello, World!');"; size_t script_size = strlen ((const char *) script); @@ -3870,7 +3928,14 @@ jerry_is_valid_cesu8_string (const jerry_char_t *cesu8_buf_p, /**< CESU-8 string **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_EMPTY); @@ -3880,9 +3945,9 @@ jerry_is_valid_cesu8_string (const jerry_char_t *cesu8_buf_p, /**< CESU-8 string if (jerry_is_valid_cesu8_string (script, (jerry_size_t) script_size)) { jerry_value_t string_value = jerry_create_string_sz (script, - (jerry_size_t) script_size)); + (jerry_size_t) script_size); - ... // usage of string_value + // usage of string_value jerry_release_value (string_value); } @@ -3935,12 +4000,19 @@ jerry_parse_and_save_snapshot (const jerry_char_t *source_p, **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_EMPTY); static uint32_t global_mode_snapshot_buffer[256]; - const jerry_char_t *code_to_snapshot_p = "(function () { return 'string from snapshot'; }) ();"; + const jerry_char_t *code_to_snapshot_p = (const jerry_char_t *) "(function () { return 'string from snapshot'; }) ();"; size_t global_mode_snapshot_size = jerry_parse_and_save_snapshot (code_to_snapshot_p, strlen ((const char *) code_to_snapshot_p), @@ -3990,11 +4062,17 @@ jerry_exec_snapshot (const uint32_t *snapshot_p, **Example** +[doctest]: # () + ```c +#include +#include "jerryscript.h" + +int +main (void) { - jerry_value_t res; static uint32_t global_mode_snapshot_buffer[256]; - const jerry_char_t *code_to_snapshot_p = "(function () { return 'string from snapshot'; }) ();"; + const jerry_char_t *code_to_snapshot_p = (const jerry_char_t *) "(function () { return 'string from snapshot'; }) ();"; jerry_init (JERRY_INIT_EMPTY); size_t global_mode_snapshot_size = jerry_parse_and_save_snapshot (code_to_snapshot_p, @@ -4007,9 +4085,10 @@ jerry_exec_snapshot (const uint32_t *snapshot_p, jerry_init (JERRY_INIT_EMPTY); - res = (jerry_exec_snapshot (global_mode_snapshot_buffer, - global_mode_snapshot_size, - false); + jerry_value_t res = jerry_exec_snapshot (global_mode_snapshot_buffer, + global_mode_snapshot_size, + false); + jerry_release_value (res); jerry_cleanup (); } @@ -4054,12 +4133,20 @@ jerry_parse_and_save_literals (const jerry_char_t *source_p, **Example** +[doctest]: # (test="link") + ```c +#include +#include +#include "jerryscript.h" + +int +main (void) { jerry_init (JERRY_INIT_EMPTY); static uint32_t save_literal_buffer[256]; - const jerry_char_t *code_for_literal_save_p = "var obj = { a:'aa', bb:'Bb' }"; + const jerry_char_t *code_for_literal_save_p = (const jerry_char_t *) "var obj = { a:'aa', bb:'Bb' }"; size_t literal_sizes = jerry_parse_and_save_literals (code_for_literal_save_p, strlen ((const char *) code_for_literal_save_p), @@ -4124,12 +4211,17 @@ jerry_set_vm_exec_stop_callback (jerry_vm_exec_stop_callback_t stop_cb, **Example** +[doctest]: # (test="link") + ```c +#include +#include "jerryscript.h" + +static int countdown = 10; + static jerry_value_t vm_exec_stop_callback (void *user_p) { - static int countdown = 10; - while (countdown > 0) { countdown--; @@ -4140,6 +4232,8 @@ vm_exec_stop_callback (void *user_p) return jerry_create_string ((const jerry_char_t *) "Abort script"); } +int +main (void) { jerry_init (JERRY_INIT_EMPTY); diff --git a/docs/03.API-EXAMPLE.md b/docs/03.API-EXAMPLE.md index d8499fd3f..936951f38 100644 --- a/docs/03.API-EXAMPLE.md +++ b/docs/03.API-EXAMPLE.md @@ -4,12 +4,14 @@ This guide is intended to introduce you to JerryScript embedding API through cre ## Step 1. Execute JavaScript from your application +[doctest]: # () + ```c #include #include "jerryscript.h" int -main (int argc, char *argv[]) +main (void) { const jerry_char_t script[] = "var str = 'Hello, World!';"; size_t script_size = strlen ((const char *) script); @@ -32,13 +34,15 @@ Here we perform the same actions, as `jerry_run_simple`, while splitting into se - engine cleanup +[doctest]: # () + ```c #include #include "jerryscript.h" #include "jerryscript-ext/handler.h" int -main (int argc, char *argv[]) +main (void) { const jerry_char_t script[] = "print ('Hello, World!');"; size_t script_size = strlen ((const char *) script); @@ -76,13 +80,15 @@ Our code is more complex now, but it introduces possibilities to interact with J ## Step 3. Execution in 'eval'-mode +[doctest]: # () + ```c #include #include "jerryscript.h" #include "jerryscript-ext/handler.h" int -main (int argc, char *argv[]) +main (void) { const jerry_char_t script_1[] = "var s = 'Hello, World!';"; const jerry_char_t script_2[] = "print (s);"; @@ -123,13 +129,16 @@ This way, we execute two independent script parts in one execution environment. ## Step 4. Interaction with JavaScript environment +[doctest]: # () + ```c #include #include "jerryscript.h" #include "jerryscript-ext/handler.h" int -main (int argc, char *argv[]) { +main (void) +{ const jerry_char_t str[] = "Hello, World!"; const jerry_char_t script[] = "print (s);"; @@ -183,6 +192,8 @@ created by API functions has the error flag set. The following example function will output a JavaScript value: +[doctest]: # (test="compile") + ```c #include #include @@ -252,6 +263,8 @@ Shell operation can be described with the following loop: - print result of eval; - loop. +[doctest]: # (test="compile") + ```c #include #include @@ -259,10 +272,10 @@ Shell operation can be described with the following loop: #include "jerryscript.h" #include "jerryscript-ext/handler.h" -static void print_value (const jerry_value_t); +void print_value (const jerry_value_t); int -main (int argc, char *argv[]) +main (void) { bool is_done = false; @@ -275,7 +288,7 @@ main (int argc, char *argv[]) while (!is_done) { - char cmd[256] = {}; + char cmd[256]; char *cmd_tail = cmd; size_t len = 0; @@ -316,7 +329,7 @@ main (int argc, char *argv[]) { /* Evaluated JS code thrown an exception * and didn't handle it with try-catch-finally */ - printf ("Unhandled JS exception occured: "); + printf ("Unhandled JS exception occurred: "); } print_value (ret_val); @@ -336,6 +349,8 @@ The application inputs commands and evaluates them, one after another. In this example we demonstrate how to use native function and structures in JavaScript. +[doctest]: # () + ```c #include #include "jerryscript.h" @@ -359,7 +374,7 @@ get_msg_handler (const jerry_value_t func_value, /**< function object */ } /* get_msg_handler */ int -main (int argc, char *argv[]) +main (void) { /* Initialize engine */ jerry_init (JERRY_INIT_EMPTY); @@ -369,7 +384,7 @@ main (int argc, char *argv[]) jerryx_handler_print); /* Do something with the native object */ - my_struct.msg = "Hello World"; + my_struct.msg = "Hello, World!"; /* Create an empty JS object */ jerry_value_t object = jerry_create_object (); @@ -427,6 +442,8 @@ Hello World Here we create a JS Object with `jerry_eval`, then extend it with a native function. This function shows how to get a property value from the object and how to manipulate it. +[doctest]: # () + ```c #include #include "jerryscript.h" @@ -466,7 +483,7 @@ add_handler (const jerry_value_t func_value, /**< function object */ } /* add_handler */ int -main (int argc, char *argv[]) +main (void) { /* Initialize engine */ jerry_init (JERRY_INIT_EMPTY); diff --git a/docs/09.EXT-REFERENCE-ARG.md b/docs/09.EXT-REFERENCE-ARG.md index bac1a6173..bd17f9c01 100644 --- a/docs/09.EXT-REFERENCE-ARG.md +++ b/docs/09.EXT-REFERENCE-ARG.md @@ -187,12 +187,18 @@ jerryx_arg_transform_this_and_args (const jerry_value_t this_val, **Example** +[doctest]: # (test="compile") + ```c +#include "jerryscript.h" +#include "jerryscript-ext/arg.h" + /* JS signature: function (requiredBool, requiredString, optionalNumber) */ -static jerry_value_t my_external_handler (const jerry_value_t function_obj, - const jerry_value_t this_val, - const jerry_value_t args_p[], - const jerry_length_t args_count) +static jerry_value_t +my_external_handler (const jerry_value_t function_obj, + const jerry_value_t this_val, + const jerry_value_t args_p[], + const jerry_length_t args_count) { bool required_bool; char required_str[16]; @@ -205,7 +211,7 @@ static jerry_value_t my_external_handler (const jerry_value_t function_obj, jerryx_arg_ignore (), jerryx_arg_boolean (&required_bool, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), - jerryx_arg_string (&required_str, sizeof (required_str), JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_string (required_str, sizeof (required_str), JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), jerryx_arg_number (&optional_num, JERRYX_ARG_NO_COERCE, JERRYX_ARG_OPTIONAL), }; @@ -226,7 +232,8 @@ static jerry_value_t my_external_handler (const jerry_value_t function_obj, * Validated and transformed successfully! * required_bool, required_str and optional_num can now be used. */ - ... + + return jerry_create_undefined (); /* Or return something more meaningful. */ } ``` @@ -498,17 +505,23 @@ jerryx_arg_object_properties (const jerryx_arg_object_props_t *object_props_p, **Example** +[doctest]: # (test="compile") + ```c +#include "jerryscript.h" +#include "jerryscript-ext/arg.h" + /** * The binding function expects args_p[0] is an object, which has 3 properties: * "enable": boolean * "data": number * "extra_data": number, optional */ -static jerry_value_t my_external_handler (const jerry_value_t function_obj, - const jerry_value_t this_val, - const jerry_value_t args_p[], - const jerry_length_t args_count) +static jerry_value_t +my_external_handler (const jerry_value_t function_obj, + const jerry_value_t this_val, + const jerry_value_t args_p[], + const jerry_length_t args_count) { bool required_bool; double required_num; @@ -519,6 +532,7 @@ static jerry_value_t my_external_handler (const jerry_value_t function_obj, /* "prop_mapping" defines the steps to transform properties to C variables. */ const jerryx_arg_t prop_mapping[] = + { jerryx_arg_boolean (&required_bool, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), jerryx_arg_number (&required_num, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), jerryx_arg_number (&optional_num, JERRYX_ARG_COERCE, JERRYX_ARG_OPTIONAL) @@ -555,7 +569,8 @@ static jerry_value_t my_external_handler (const jerry_value_t function_obj, * Validated and transformed successfully! * required_bool, required_num and optional_num can now be used. */ - ... + + return jerry_create_undefined (); /* Or return something more meaningful. */ } ``` diff --git a/docs/10.EXT-REFERENCE-HANDLER.md b/docs/10.EXT-REFERENCE-HANDLER.md index fff6e7bd7..431ab8bed 100644 --- a/docs/10.EXT-REFERENCE-HANDLER.md +++ b/docs/10.EXT-REFERENCE-HANDLER.md @@ -114,6 +114,8 @@ jerryx_handler_register_global (const jerry_char_t *name_p, **Example** +[doctest]: # (test="compile") + ```c #include "jerryscript.h" #include "jerryscript-ext/handler.h" @@ -129,7 +131,8 @@ static const struct { { NULL, NULL } }; -static void register_common_functions () +static void +register_common_functions (void) { jerry_value_t ret = jerry_create_undefined (); @@ -139,7 +142,7 @@ static void register_common_functions () common_functions[i].handler_p); } - return ret; + jerry_release_value (ret); } ``` diff --git a/docs/11.EXT-REFERENCE-AUTORELEASE.md b/docs/11.EXT-REFERENCE-AUTORELEASE.md index 8e3aa9ae3..b2cc6b662 100644 --- a/docs/11.EXT-REFERENCE-AUTORELEASE.md +++ b/docs/11.EXT-REFERENCE-AUTORELEASE.md @@ -12,19 +12,22 @@ using the `__cleanup__` variable attribute. For other compilers, no support has **Example** +[doctest]: # (test="compile") + ```c #include "jerryscript.h" #include "jerryscript-ext/autorelease.h" -static void foo (bool enable) +static void +foo (bool enable) { - JERRYX_AR_VALUE_T bar = jerry_create_string (...); + JERRYX_AR_VALUE_T bar = jerry_create_string ((const jerry_char_t *) "..."); if (enable) { JERRYX_AR_VALUE_T baz = jerry_get_global_object (); - ... + /* bar and baz can now be used. */ /* * jerry_release_value (baz) and jerry_release_value (bar) is called automatically before diff --git a/tests/unit-doc/CMakeLists.txt b/tests/unit-doc/CMakeLists.txt new file mode 100644 index 000000000..5380324f7 --- /dev/null +++ b/tests/unit-doc/CMakeLists.txt @@ -0,0 +1,89 @@ +# Copyright JS Foundation and other contributors, http://js.foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8.12) +project (unit-doc C) + +set(GEN_DOCTEST "${CMAKE_SOURCE_DIR}/tools/gen-doctest.py") +file(GLOB DOC_FILES "${CMAKE_SOURCE_DIR}/docs/*.md") + +set(COMPILE_FLAGS_DOCTEST "-Wno-unused-parameter -Wno-unused-function -Wno-unused-variable") + +# Dry run of the doctest generator: analyze MarkDown files and get the list of +# file names that will be generated. This allows the definition of proper +# dependencies between the MarkDown files and the generated sources. +execute_process( + COMMAND ${GEN_DOCTEST} --dry -d ${CMAKE_CURRENT_SOURCE_DIR} ${DOC_FILES} + OUTPUT_VARIABLE DOCTEST_OUTPUT + RESULT_VARIABLE GEN_DOCTEST_RESULT +) +if(NOT GEN_DOCTEST_RESULT EQUAL 0) + message(FATAL_ERROR "failed to get doctest file list") +endif() + +# Process the output of the doctest generator: collect sources that must be +# compiled, compiled+linked, or compiled+linked+executed into separate lists. +set(DOCTEST_COMPILE "") +set(DOCTEST_LINK "") +set(DOCTEST_RUN "") + +string(REPLACE "\n" ";" DOCTEST_LIST "${DOCTEST_OUTPUT}") +foreach(DOCTEST_ELEMENT IN LISTS DOCTEST_LIST) + if(NOT ("${DOCTEST_ELEMENT}" STREQUAL "")) + separate_arguments(DOCTEST_ELEMENT) + list(GET DOCTEST_ELEMENT 0 DOCTEST_ACTION) + list(GET DOCTEST_ELEMENT 1 DOCTEST_NAME) + string(TOUPPER ${DOCTEST_ACTION} DOCTEST_ACTION) + + set(DOCTEST_${DOCTEST_ACTION} ${DOCTEST_${DOCTEST_ACTION}} ${DOCTEST_NAME}) + endif() +endforeach() + +# Add custom command to run doctest generator if any of the MarkDown sources +# changes. +add_custom_command( + COMMAND ${GEN_DOCTEST} -d ${CMAKE_CURRENT_SOURCE_DIR} ${DOC_FILES} + DEPENDS ${GEN_DOCTEST} ${DOC_FILES} + OUTPUT ${DOCTEST_COMPILE} ${DOCTEST_LINK} ${DOCTEST_RUN} + COMMENT "Generating doctests" +) + +# Process compile-only doctests: add them to a dummy library +# (named libcompile-doc-tests.a) to trigger compilation. +if(NOT ("${DOCTEST_COMPILE}" STREQUAL "")) + add_library(compile-doc-tests STATIC ${DOCTEST_COMPILE}) + target_link_libraries(compile-doc-tests jerry-ext jerry-core jerry-port-default-minimal) + set_property(TARGET compile-doc-tests APPEND_STRING PROPERTY COMPILE_FLAGS "${COMPILE_FLAGS_DOCTEST}") +endif() + +macro(doctest_add_executables NAME_PREFIX) + foreach(DOCTEST_NAME ${ARGN}) + get_filename_component(TARGET_NAME ${DOCTEST_NAME} NAME) + string(REGEX REPLACE "\\.[^.]*$" "" TARGET_NAME ${TARGET_NAME}) + set(TARGET_NAME ${NAME_PREFIX}-${TARGET_NAME}) + + add_executable(${TARGET_NAME} ${DOCTEST_NAME}) + set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY COMPILE_FLAGS "${COMPILE_FLAGS_DOCTEST}") + set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${LINKER_FLAGS_COMMON}") + target_link_libraries(${TARGET_NAME} jerry-ext jerry-core jerry-port-default-minimal) + endforeach() +endmacro() + +# Process link-only doctests: create an executable from each (named link-doc-*) +doctest_add_executables("link-doc" ${DOCTEST_LINK}) + +# Process "full-fledged" doctests: create an executable from each (named +# unit-doc-*). Their name prefix (unit-) ensures that the unit test runner +# script will treat them like any other unit test, i.e., executed them. +doctest_add_executables("unit-doc" ${DOCTEST_RUN}) diff --git a/tools/build.py b/tools/build.py index 16a8de665..2db7d22c9 100755 --- a/tools/build.py +++ b/tools/build.py @@ -62,6 +62,8 @@ def get_arguments(): help='enable 32 bit compressed pointers (%(choices)s; default: %(default)s)') parser.add_argument('--debug', action='store_const', const='Debug', default='MinSizeRel', dest='build_type', help='debug build') + parser.add_argument('--doctests', action='store_const', const='ON', default='OFF', + help='build doctests') parser.add_argument('--error-messages', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper, help='enable error messages (%(choices)s; default: %(default)s)') parser.add_argument('--external-context', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper, @@ -172,6 +174,7 @@ def generate_build_options(arguments): build_options.append('-DCMAKE_TOOLCHAIN_FILE=%s' % arguments.toolchain) build_options.append('-DUNITTESTS=%s' % arguments.unittests) + build_options.append('-DDOCTESTS=%s' % arguments.doctests) build_options.append('-DCMAKE_VERBOSE_MAKEFILE=%s' % arguments.verbose) # developer options diff --git a/tools/gen-doctest.py b/tools/gen-doctest.py new file mode 100755 index 000000000..9e3c19667 --- /dev/null +++ b/tools/gen-doctest.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python + +# Copyright JS Foundation and other contributors, http://js.foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import argparse +import fileinput +import os +import re +import shlex +import sys + + +class DoctestExtractor(object): + """ + An extractor to process Markdown files and find doctests inside. + """ + + def __init__(self, outdir, dry): + """ + :param outdir: path to the directory where to write the found doctests. + :param dry: if True, don't create the doctest files but print the file + names only. + """ + self._outdir = outdir + self._dry = dry + + # Attributes actually initialized by process() + self._infile = None + self._outname_base = None + self._outname_cnt = None + + def _warning(self, message, lineno): + """ + Print a warning to the standard error. + + :param message: a description of the problem. + :param lineno: the location that triggered the warning. + """ + print('%s:%d: %s' % (self._infile, lineno, message), file=sys.stderr) + + def _process_decl(self, params): + """ + Process a doctest declaration (`[doctest]: # (name="test.c", ...)`). + + :param params: the parameter string of the declaration (the string + between the parentheses). + :return: a tuple of a dictionary (of keys and values taken from the + `params` string) and the line number of the declaration. + """ + tokens = list(shlex.shlex(params)) + + decl = {} + for i in range(0, len(tokens), 4): + if i + 2 >= len(tokens) or tokens[i + 1] != '=' or (i + 3 < len(tokens) and tokens[i + 3] != ','): + self._warning('incorrect parameter list for test (key="value", ...)', fileinput.filelineno()) + decl = {} + break + decl[tokens[i]] = tokens[i + 2].strip('\'"') + + if 'name' not in decl: + decl['name'] = '%s%d.c' % (self._outname_base, self._outname_cnt) + self._outname_cnt += 1 + + if 'test' not in decl: + decl['test'] = 'run' + + return decl, fileinput.filelineno() + + def _process_code_start(self): + """ + Process the beginning of a fenced code block (` ```c `). + + :return: a tuple of a list (of the first line(s) of the doctest) and the + line number of the start of the code block. + """ + return ['#line %d "%s"\n' % (fileinput.filelineno() + 1, self._infile)], fileinput.filelineno() + + def _process_code_end(self, decl, code): + """ + Process the end of a fenced code block (` ``` `). + + :param decl: the dictionary of the declaration parameters. + :param code: the list of lines of the doctest. + """ + outname = os.path.join(self._outdir, decl['name']) + action = decl['test'] + if self._dry: + print('%s %s' % (action, outname)) + else: + with open(outname, 'w') as outfile: + outfile.writelines(code) + + def process(self, infile): + """ + Find doctests in a Markdown file and process them according to the + constructor parameters. + + :param infile: path to the input file. + """ + self._infile = infile + self._outname_base = os.path.splitext(os.path.basename(infile))[0] + self._outname_cnt = 1 + + mode = 'TEXT' + decl, decl_lineno = {}, 0 + code, code_lineno = [], 0 + + for line in fileinput.input(infile): + decl_match = re.match(r'^\[doctest\]:\s+#\s+\((.*)\)\s*$', line) + nl_match = re.match(r'^\s*$', line) + start_match = re.match(r'^```c\s*$', line) + end_match = re.match(r'^```\s*', line) + + if mode == 'TEXT': + if decl_match is not None: + decl, decl_lineno = self._process_decl(decl_match.group(1)) + mode = 'NL' + elif mode == 'NL': + if decl_match is not None: + self._warning('test without code block', decl_lineno) + decl, decl_lineno = self._process_decl(decl_match.group(1)) + elif start_match is not None: + code, code_lineno = self._process_code_start() + mode = 'CODE' + elif nl_match is None: + self._warning('test without code block', decl_lineno) + mode = 'TEXT' + elif mode == 'CODE': + if end_match is not None: + self._process_code_end(decl, code) + mode = 'TEXT' + else: + code.append(line) + + if mode == 'NL': + self._warning('test without code block', decl_lineno) + elif mode == 'CODE': + self._warning('unterminated code block', code_lineno) + + +def main(): + parser = argparse.ArgumentParser(description='Markdown doctest extractor', epilog=""" + The tool extracts specially marked fenced C code blocks from the input Markdown files + and writes them to the file system. The annotations recognized by the tool are special + but valid Markdown links/comments that must be added before the fenced code blocks: + `[doctest]: # (name="test.c", ...)`. For now, two parameters are valid: + `name` determines the filename for the extracted code block (overriding the default + auto-numbered naming scheme), and `test` determines the test action to be performed on + the extracted code (valid options are "compile", "link", and the default "run"). + """) + parser.add_argument('-d', '--dir', metavar='NAME', default=os.getcwd(), + help='output directory name (default: %(default)s)') + parser.add_argument('--dry', action='store_true', + help='don\'t generate files but print file names that would be generated ' + 'and what test action to perform on them') + parser.add_argument('file', nargs='+', + help='input Markdown file(s)') + args = parser.parse_args() + + extractor = DoctestExtractor(args.dir, args.dry) + for mdfile in args.file: + extractor.process(mdfile) + + +if __name__ == '__main__': + main() diff --git a/tools/run-tests.py b/tools/run-tests.py index b562465f6..e05e57f91 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -42,9 +42,13 @@ def get_binary_path(bin_dir_path): # Test options for unittests JERRY_UNITTESTS_OPTIONS = [ Options('unittests', - ['--unittests', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on', '--profile=es2015-subset']), + ['--unittests', '--jerry-cmdline=off', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on', '--profile=es2015-subset']), Options('unittests-debug', - ['--unittests', '--debug', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on', '--profile=es2015-subset']) + ['--unittests', '--jerry-cmdline=off', '--debug', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on', '--profile=es2015-subset']), + Options('doctests', + ['--doctests', '--jerry-cmdline=off', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on', '--profile=es2015-subset']), + Options('doctests-debug', + ['--doctests', '--jerry-cmdline=off', '--debug', '--error-messages=on', '--snapshot-save=on', '--snapshot-exec=on', '--vm-exec-stop=on', '--profile=es2015-subset']) ] # Test options for jerry-tests @@ -152,7 +156,7 @@ def get_arguments(): parser.add_argument('--jerry-debugger', action='store_true', default=False, help='Run jerry-debugger tests') parser.add_argument('--jerry-tests', action='store_true', default=False, help='Run jerry-tests') parser.add_argument('--jerry-test-suite', action='store_true', default=False, help='Run jerry-test-suite') - parser.add_argument('--unittests', action='store_true', default=False, help='Run unittests') + parser.add_argument('--unittests', action='store_true', default=False, help='Run unittests (including doctests)') parser.add_argument('--precommit', action='store_true', default=False, dest='all', help='Run all test') parser.add_argument('--test262', action='store_true', default=False, help='Run test262')