Add support for doctests (#1909)

Markdown files in the docs/ directory can now be annotated to turn
fenced C code blocks into unit tests. The recognized syntax is:

    [doctest]: # (name="test.c", test="run")

    ```c
    // unit test code
    ```

The commit also fixes the issues revealed during the initial
annotation.

JerryScript-DCO-1.0-Signed-off-by: Akos Kiss akiss@inf.u-szeged.hu
This commit is contained in:
Akos Kiss
2017-07-14 16:18:20 +02:00
committed by GitHub
parent 3d744c958f
commit 00f93bc287
11 changed files with 468 additions and 50 deletions
+1
View File
@@ -42,3 +42,4 @@ docs/doxygen
# Tests # Tests
tests/test262/ tests/test262/
tests/unit-doc/*.c
+13 -4
View File
@@ -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_LIBC ON CACHE BOOL "Build and use jerry-libc?")
set(JERRY_LIBM ON CACHE BOOL "Build and use jerry-libm?") set(JERRY_LIBM ON CACHE BOOL "Build and use jerry-libm?")
set(UNITTESTS OFF CACHE BOOL "Build unit tests?") set(UNITTESTS OFF CACHE BOOL "Build unit tests?")
set(DOCTESTS OFF CACHE BOOL "Build doc tests?")
# Optional build settings # Optional build settings
set(ENABLE_ALL_IN_ONE OFF CACHE BOOL "Enable all-in-one build?") 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?") set(ENABLE_STRIP ON CACHE BOOL "Enable stripping all symbols from release binary?")
# Option overrides # 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 ON)
set(JERRY_PORT_DEFAULT_MESSAGE " (FORCED BY CMDLINE)") set(JERRY_PORT_DEFAULT_MESSAGE " (FORCED BY CMDLINE OR TESTS)")
endif() endif()
if(JERRY_CMDLINE) if(JERRY_CMDLINE OR DOCTESTS)
set(JERRY_EXT ON) set(JERRY_EXT ON)
set(JERRY_EXT_MESSAGE " (FORCED BY CMDLINE)") set(JERRY_EXT_MESSAGE " (FORCED BY CMDLINE OR TESTS)")
endif() endif()
if("${PLATFORM}" STREQUAL "DARWIN") 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_LIBC " ${JERRY_LIBC} ${JERRY_LIBC_MESSAGE})
message(STATUS "JERRY_LIBM " ${JERRY_LIBM} ${JERRY_LIBM_MESSAGE}) message(STATUS "JERRY_LIBM " ${JERRY_LIBM} ${JERRY_LIBM_MESSAGE})
message(STATUS "UNITTESTS " ${UNITTESTS}) message(STATUS "UNITTESTS " ${UNITTESTS})
message(STATUS "DOCTESTS " ${DOCTESTS})
# Setup directories # Setup directories
# Project binary dir # Project binary dir
@@ -180,6 +182,8 @@ if (USING_GCC OR USING_CLANG)
endif() endif()
if(("${PLATFORM}" STREQUAL "DARWIN")) if(("${PLATFORM}" STREQUAL "DARWIN"))
jerry_add_link_flags(-lSystem) jerry_add_link_flags(-lSystem)
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Sqc <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
else() else()
jerry_add_link_flags(-Wl,-z,noexecstack) jerry_add_link_flags(-Wl,-z,noexecstack)
endif() endif()
@@ -274,3 +278,8 @@ if(UNITTESTS)
add_subdirectory(tests/unit-ext) add_subdirectory(tests/unit-ext)
endif() endif()
endif() endif()
# Doctests
if(DOCTESTS)
add_subdirectory(tests/unit-doc)
endif()
+110 -16
View File
@@ -303,7 +303,13 @@ jerry_init (jerry_init_flag_t flags)
**Example** **Example**
[doctest]: # ()
```c ```c
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_SHOW_OPCODES | JERRY_INIT_SHOW_REGEXP_OPCODES); 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** **Example**
[doctest]: # (test="compile")
```c ```c
#include "jerryscript.h"
typedef struct typedef struct
{ {
int my_data1; 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 * 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 * 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); 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 */ /* 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** **Example**
[doctest]: # ()
```c ```c
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); 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 // must be static, because 'jerry_register_magic_strings' does not copy
static const jerry_length_t magic_string_lengths[] = { static const jerry_length_t magic_string_lengths[] = {
(jerry_length_t)strlen (magic_string_items[0]), 12,
(jerry_length_t)strlen (magic_string_items[1]), 12,
(jerry_length_t)strlen (magic_string_items[2]) 12
}; };
jerry_register_magic_strings (magic_string_items, num_magic_string_items, magic_string_lengths); 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** **Example**
[doctest]: # ()
```c ```c
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); jerry_init (JERRY_INIT_EMPTY);
@@ -552,9 +575,16 @@ jerry_run_simple (const jerry_char_t *script_source_p,
**Example** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#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); jerry_run_simple (script, strlen ((const char *) script), JERRY_INIT_EMPTY);
} }
@@ -596,7 +626,14 @@ jerry_parse (const jerry_char_t *source_p,
**Example** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); jerry_init (JERRY_INIT_EMPTY);
@@ -671,7 +708,14 @@ jerry_run (const jerry_value_t func_val);
**Example** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
const jerry_char_t script[] = "print ('Hello, World!');"; const jerry_char_t script[] = "print ('Hello, World!');";
size_t script_size = strlen ((const char *) script); size_t script_size = strlen ((const char *) script);
@@ -756,7 +800,14 @@ jerry_run_all_enqueued_jobs (void)
**Example** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); 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** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
const jerry_char_t script[] = "print ('Hello, World!');"; const jerry_char_t script[] = "print ('Hello, World!');";
size_t script_size = strlen ((const char *) script); 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** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); 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)) if (jerry_is_valid_cesu8_string (script, (jerry_size_t) script_size))
{ {
jerry_value_t string_value = jerry_create_string_sz (script, 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); jerry_release_value (string_value);
} }
@@ -3935,12 +4000,19 @@ jerry_parse_and_save_snapshot (const jerry_char_t *source_p,
**Example** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); jerry_init (JERRY_INIT_EMPTY);
static uint32_t global_mode_snapshot_buffer[256]; 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, size_t global_mode_snapshot_size = jerry_parse_and_save_snapshot (code_to_snapshot_p,
strlen ((const char *) code_to_snapshot_p), strlen ((const char *) code_to_snapshot_p),
@@ -3990,11 +4062,17 @@ jerry_exec_snapshot (const uint32_t *snapshot_p,
**Example** **Example**
[doctest]: # ()
```c ```c
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
jerry_value_t res;
static uint32_t global_mode_snapshot_buffer[256]; 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); jerry_init (JERRY_INIT_EMPTY);
size_t global_mode_snapshot_size = jerry_parse_and_save_snapshot (code_to_snapshot_p, 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); jerry_init (JERRY_INIT_EMPTY);
res = (jerry_exec_snapshot (global_mode_snapshot_buffer, jerry_value_t res = jerry_exec_snapshot (global_mode_snapshot_buffer,
global_mode_snapshot_size, global_mode_snapshot_size,
false); false);
jerry_release_value (res);
jerry_cleanup (); jerry_cleanup ();
} }
@@ -4054,12 +4133,20 @@ jerry_parse_and_save_literals (const jerry_char_t *source_p,
**Example** **Example**
[doctest]: # (test="link")
```c ```c
#include <stdio.h>
#include <string.h>
#include "jerryscript.h"
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); jerry_init (JERRY_INIT_EMPTY);
static uint32_t save_literal_buffer[256]; 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, size_t literal_sizes = jerry_parse_and_save_literals (code_for_literal_save_p,
strlen ((const char *) 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** **Example**
[doctest]: # (test="link")
```c ```c
#include <string.h>
#include "jerryscript.h"
static int countdown = 10;
static jerry_value_t static jerry_value_t
vm_exec_stop_callback (void *user_p) vm_exec_stop_callback (void *user_p)
{ {
static int countdown = 10;
while (countdown > 0) while (countdown > 0)
{ {
countdown--; countdown--;
@@ -4140,6 +4232,8 @@ vm_exec_stop_callback (void *user_p)
return jerry_create_string ((const jerry_char_t *) "Abort script"); return jerry_create_string ((const jerry_char_t *) "Abort script");
} }
int
main (void)
{ {
jerry_init (JERRY_INIT_EMPTY); jerry_init (JERRY_INIT_EMPTY);
+28 -11
View File
@@ -4,12 +4,14 @@ This guide is intended to introduce you to JerryScript embedding API through cre
## Step 1. Execute JavaScript from your application ## Step 1. Execute JavaScript from your application
[doctest]: # ()
```c ```c
#include <string.h> #include <string.h>
#include "jerryscript.h" #include "jerryscript.h"
int int
main (int argc, char *argv[]) main (void)
{ {
const jerry_char_t script[] = "var str = 'Hello, World!';"; const jerry_char_t script[] = "var str = 'Hello, World!';";
size_t script_size = strlen ((const char *) script); 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 - engine cleanup
[doctest]: # ()
```c ```c
#include <string.h> #include <string.h>
#include "jerryscript.h" #include "jerryscript.h"
#include "jerryscript-ext/handler.h" #include "jerryscript-ext/handler.h"
int int
main (int argc, char *argv[]) main (void)
{ {
const jerry_char_t script[] = "print ('Hello, World!');"; const jerry_char_t script[] = "print ('Hello, World!');";
size_t script_size = strlen ((const char *) script); 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 ## Step 3. Execution in 'eval'-mode
[doctest]: # ()
```c ```c
#include <string.h> #include <string.h>
#include "jerryscript.h" #include "jerryscript.h"
#include "jerryscript-ext/handler.h" #include "jerryscript-ext/handler.h"
int int
main (int argc, char *argv[]) main (void)
{ {
const jerry_char_t script_1[] = "var s = 'Hello, World!';"; const jerry_char_t script_1[] = "var s = 'Hello, World!';";
const jerry_char_t script_2[] = "print (s);"; 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 ## Step 4. Interaction with JavaScript environment
[doctest]: # ()
```c ```c
#include <string.h> #include <string.h>
#include "jerryscript.h" #include "jerryscript.h"
#include "jerryscript-ext/handler.h" #include "jerryscript-ext/handler.h"
int int
main (int argc, char *argv[]) { main (void)
{
const jerry_char_t str[] = "Hello, World!"; const jerry_char_t str[] = "Hello, World!";
const jerry_char_t script[] = "print (s);"; 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: The following example function will output a JavaScript value:
[doctest]: # (test="compile")
```c ```c
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -252,6 +263,8 @@ Shell operation can be described with the following loop:
- print result of eval; - print result of eval;
- loop. - loop.
[doctest]: # (test="compile")
```c ```c
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -259,10 +272,10 @@ Shell operation can be described with the following loop:
#include "jerryscript.h" #include "jerryscript.h"
#include "jerryscript-ext/handler.h" #include "jerryscript-ext/handler.h"
static void print_value (const jerry_value_t); void print_value (const jerry_value_t);
int int
main (int argc, char *argv[]) main (void)
{ {
bool is_done = false; bool is_done = false;
@@ -275,7 +288,7 @@ main (int argc, char *argv[])
while (!is_done) while (!is_done)
{ {
char cmd[256] = {}; char cmd[256];
char *cmd_tail = cmd; char *cmd_tail = cmd;
size_t len = 0; size_t len = 0;
@@ -316,7 +329,7 @@ main (int argc, char *argv[])
{ {
/* Evaluated JS code thrown an exception /* Evaluated JS code thrown an exception
* and didn't handle it with try-catch-finally */ * and didn't handle it with try-catch-finally */
printf ("Unhandled JS exception occured: "); printf ("Unhandled JS exception occurred: ");
} }
print_value (ret_val); 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. In this example we demonstrate how to use native function and structures in JavaScript.
[doctest]: # ()
```c ```c
#include <string.h> #include <string.h>
#include "jerryscript.h" #include "jerryscript.h"
@@ -359,7 +374,7 @@ get_msg_handler (const jerry_value_t func_value, /**< function object */
} /* get_msg_handler */ } /* get_msg_handler */
int int
main (int argc, char *argv[]) main (void)
{ {
/* Initialize engine */ /* Initialize engine */
jerry_init (JERRY_INIT_EMPTY); jerry_init (JERRY_INIT_EMPTY);
@@ -369,7 +384,7 @@ main (int argc, char *argv[])
jerryx_handler_print); jerryx_handler_print);
/* Do something with the native object */ /* Do something with the native object */
my_struct.msg = "Hello World"; my_struct.msg = "Hello, World!";
/* Create an empty JS object */ /* Create an empty JS object */
jerry_value_t object = jerry_create_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. 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 ```c
#include <string.h> #include <string.h>
#include "jerryscript.h" #include "jerryscript.h"
@@ -466,7 +483,7 @@ add_handler (const jerry_value_t func_value, /**< function object */
} /* add_handler */ } /* add_handler */
int int
main (int argc, char *argv[]) main (void)
{ {
/* Initialize engine */ /* Initialize engine */
jerry_init (JERRY_INIT_EMPTY); jerry_init (JERRY_INIT_EMPTY);
+26 -11
View File
@@ -187,12 +187,18 @@ jerryx_arg_transform_this_and_args (const jerry_value_t this_val,
**Example** **Example**
[doctest]: # (test="compile")
```c ```c
#include "jerryscript.h"
#include "jerryscript-ext/arg.h"
/* JS signature: function (requiredBool, requiredString, optionalNumber) */ /* JS signature: function (requiredBool, requiredString, optionalNumber) */
static jerry_value_t my_external_handler (const jerry_value_t function_obj, static jerry_value_t
const jerry_value_t this_val, my_external_handler (const jerry_value_t function_obj,
const jerry_value_t args_p[], const jerry_value_t this_val,
const jerry_length_t args_count) const jerry_value_t args_p[],
const jerry_length_t args_count)
{ {
bool required_bool; bool required_bool;
char required_str[16]; 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_ignore (),
jerryx_arg_boolean (&required_bool, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), 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), 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! * Validated and transformed successfully!
* required_bool, required_str and optional_num can now be used. * 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** **Example**
[doctest]: # (test="compile")
```c ```c
#include "jerryscript.h"
#include "jerryscript-ext/arg.h"
/** /**
* The binding function expects args_p[0] is an object, which has 3 properties: * The binding function expects args_p[0] is an object, which has 3 properties:
* "enable": boolean * "enable": boolean
* "data": number * "data": number
* "extra_data": number, optional * "extra_data": number, optional
*/ */
static jerry_value_t my_external_handler (const jerry_value_t function_obj, static jerry_value_t
const jerry_value_t this_val, my_external_handler (const jerry_value_t function_obj,
const jerry_value_t args_p[], const jerry_value_t this_val,
const jerry_length_t args_count) const jerry_value_t args_p[],
const jerry_length_t args_count)
{ {
bool required_bool; bool required_bool;
double required_num; 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. */ /* "prop_mapping" defines the steps to transform properties to C variables. */
const jerryx_arg_t prop_mapping[] = const jerryx_arg_t prop_mapping[] =
{
jerryx_arg_boolean (&required_bool, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), 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 (&required_num, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED),
jerryx_arg_number (&optional_num, JERRYX_ARG_COERCE, JERRYX_ARG_OPTIONAL) 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! * Validated and transformed successfully!
* required_bool, required_num and optional_num can now be used. * required_bool, required_num and optional_num can now be used.
*/ */
...
return jerry_create_undefined (); /* Or return something more meaningful. */
} }
``` ```
+5 -2
View File
@@ -114,6 +114,8 @@ jerryx_handler_register_global (const jerry_char_t *name_p,
**Example** **Example**
[doctest]: # (test="compile")
```c ```c
#include "jerryscript.h" #include "jerryscript.h"
#include "jerryscript-ext/handler.h" #include "jerryscript-ext/handler.h"
@@ -129,7 +131,8 @@ static const struct {
{ NULL, NULL } { NULL, NULL }
}; };
static void register_common_functions () static void
register_common_functions (void)
{ {
jerry_value_t ret = jerry_create_undefined (); jerry_value_t ret = jerry_create_undefined ();
@@ -139,7 +142,7 @@ static void register_common_functions ()
common_functions[i].handler_p); common_functions[i].handler_p);
} }
return ret; jerry_release_value (ret);
} }
``` ```
+6 -3
View File
@@ -12,19 +12,22 @@ using the `__cleanup__` variable attribute. For other compilers, no support has
**Example** **Example**
[doctest]: # (test="compile")
```c ```c
#include "jerryscript.h" #include "jerryscript.h"
#include "jerryscript-ext/autorelease.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) if (enable)
{ {
JERRYX_AR_VALUE_T baz = jerry_get_global_object (); 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 * jerry_release_value (baz) and jerry_release_value (bar) is called automatically before
+89
View File
@@ -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})
+3
View File
@@ -62,6 +62,8 @@ def get_arguments():
help='enable 32 bit compressed pointers (%(choices)s; default: %(default)s)') 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', parser.add_argument('--debug', action='store_const', const='Debug', default='MinSizeRel', dest='build_type',
help='debug build') 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, parser.add_argument('--error-messages', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper,
help='enable error messages (%(choices)s; default: %(default)s)') help='enable error messages (%(choices)s; default: %(default)s)')
parser.add_argument('--external-context', metavar='X', choices=['ON', 'OFF'], default='OFF', type=str.upper, 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('-DCMAKE_TOOLCHAIN_FILE=%s' % arguments.toolchain)
build_options.append('-DUNITTESTS=%s' % arguments.unittests) build_options.append('-DUNITTESTS=%s' % arguments.unittests)
build_options.append('-DDOCTESTS=%s' % arguments.doctests)
build_options.append('-DCMAKE_VERBOSE_MAKEFILE=%s' % arguments.verbose) build_options.append('-DCMAKE_VERBOSE_MAKEFILE=%s' % arguments.verbose)
# developer options # developer options
+180
View File
@@ -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()
+7 -3
View File
@@ -42,9 +42,13 @@ def get_binary_path(bin_dir_path):
# Test options for unittests # Test options for unittests
JERRY_UNITTESTS_OPTIONS = [ JERRY_UNITTESTS_OPTIONS = [
Options('unittests', 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', 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 # 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-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-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('--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('--precommit', action='store_true', default=False, dest='all', help='Run all test')
parser.add_argument('--test262', action='store_true', default=False, help='Run test262') parser.add_argument('--test262', action='store_true', default=False, help='Run test262')