diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ffb45c9a20cd..8e37e9f5bc72 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,8 +20,6 @@ jobs: uses: actions/checkout@v6 - name: Install dependencies run: pip install -r docs/requirements.txt - - name: Check formatting - run: make -C docs check-formatting - name: Publish if: github.event_name == 'push' uses: sphinx-notes/pages@v3 diff --git a/docs-old/input-filter.md b/docs-old/input-filter.md deleted file mode 100644 index b4df9a6e77df..000000000000 --- a/docs-old/input-filter.md +++ /dev/null @@ -1,180 +0,0 @@ -# Input filter support in PHP - -XSS (Cross Site Scripting) hacks are becoming more and more prevalent, and can -be quite difficult to prevent. Whenever you accept user data and somehow display -this data back to users, you are likely vulnerable to XSS hacks. - -The Input Filter support in PHP is aimed at providing the framework through -which a company-wide or site-wide security policy can be enforced. It is -implemented as a SAPI hook and is called from the `treat_data` and post handler -functions. To implement your own security policy you will need to write a -standard PHP extension. There is also a powerful standard implementation in -`ext/filter` that should suit most peoples' needs. However, if you want to -implement your own security policy, read on. - -A simple implementation might look like the following. This stores the original -raw user data and adds a `my_get_raw()` function while the normal `$_POST`, -`$_GET` and `$_COOKIE` arrays are only populated with stripped data. In this -simple example all I am doing is calling `strip_tags()` on the data. - -```c -ZEND_BEGIN_MODULE_GLOBALS(my_input_filter) - zval *post_array; - zval *get_array; - zval *cookie_array; -ZEND_END_MODULE_GLOBALS(my_input_filter) - -#ifdef ZTS -#define IF_G(v) TSRMG(my_input_filter_globals_id, zend_my_input_filter_globals *, v) -#else -#define IF_G(v) (my_input_filter_globals.v) -#endif - -ZEND_DECLARE_MODULE_GLOBALS(my_input_filter) - -zend_function_entry my_input_filter_functions[] = { - PHP_FE(my_get_raw, NULL) - {NULL, NULL, NULL} -}; - -zend_module_entry my_input_filter_module_entry = { - STANDARD_MODULE_HEADER, - "my_input_filter", - my_input_filter_functions, - PHP_MINIT(my_input_filter), - PHP_MSHUTDOWN(my_input_filter), - NULL, - PHP_RSHUTDOWN(my_input_filter), - PHP_MINFO(my_input_filter), - "0.1", - STANDARD_MODULE_PROPERTIES -}; - -PHP_MINIT_FUNCTION(my_input_filter) -{ - ZEND_INIT_MODULE_GLOBALS(my_input_filter, php_my_input_filter_init_globals, NULL); - - REGISTER_LONG_CONSTANT("POST", PARSE_POST, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("GET", PARSE_GET, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("COOKIE", PARSE_COOKIE, CONST_PERSISTENT); - - sapi_register_input_filter(my_sapi_input_filter); - return SUCCESS; -} - -PHP_RSHUTDOWN_FUNCTION(my_input_filter) -{ - if(IF_G(get_array)) { - zval_ptr_dtor(&IF_G(get_array)); - IF_G(get_array) = NULL; - } - if(IF_G(post_array)) { - zval_ptr_dtor(&IF_G(post_array)); - IF_G(post_array) = NULL; - } - if(IF_G(cookie_array)) { - zval_ptr_dtor(&IF_G(cookie_array)); - IF_G(cookie_array) = NULL; - } - return SUCCESS; -} - -PHP_MINFO_FUNCTION(my_input_filter) -{ - php_info_print_table_start(); - php_info_print_table_row( 2, "My Input Filter Support", "enabled" ); - php_info_print_table_end(); -} - -/* The filter handler. If you return 1 from it, then PHP also registers the - * (modified) variable. Returning 0 prevents PHP from registering the variable; - * you can use this if your filter already registers the variable under a - * different name, or if you just don't want the variable registered at all. */ -SAPI_INPUT_FILTER_FUNC(my_sapi_input_filter) -{ - zval new_var; - zval *array_ptr = NULL; - char *raw_var; - int var_len; - - assert(*val != NULL); - - switch(arg) { - case PARSE_GET: - if(!IF_G(get_array)) { - ALLOC_ZVAL(array_ptr); - array_init(array_ptr); - INIT_PZVAL(array_ptr); - } - IF_G(get_array) = array_ptr; - break; - case PARSE_POST: - if(!IF_G(post_array)) { - ALLOC_ZVAL(array_ptr); - array_init(array_ptr); - INIT_PZVAL(array_ptr); - } - IF_G(post_array) = array_ptr; - break; - case PARSE_COOKIE: - if(!IF_G(cookie_array)) { - ALLOC_ZVAL(array_ptr); - array_init(array_ptr); - INIT_PZVAL(array_ptr); - } - IF_G(cookie_array) = array_ptr; - break; - } - Z_STRLEN(new_var) = val_len; - Z_STRVAL(new_var) = estrndup(*val, val_len); - Z_TYPE(new_var) = IS_STRING; - - var_len = strlen(var); - raw_var = emalloc(var_len+5); /* RAW_ and a \0 */ - strcpy(raw_var, "RAW_"); - strlcat(raw_var,var,var_len+5); - - php_register_variable_ex(raw_var, &new_var, array_ptr); - - php_strip_tags(*val, val_len, NULL, NULL, 0); - - *new_val_len = strlen(*val); - return 1; -} - -PHP_FUNCTION(my_get_raw) -{ - long arg; - char *var; - int var_len; - zval **tmp; - zval *array_ptr = NULL; - - if(zend_parse_parameters(2, "ls", &arg, &var, &var_len) == FAILURE) { - return; - } - - switch(arg) { - case PARSE_GET: - array_ptr = IF_G(get_array); - break; - case PARSE_POST: - array_ptr = IF_G(post_array); - break; - case PARSE_COOKIE: - array_ptr = IF_G(post_array); - break; - } - - if(!array_ptr) { - RETURN_FALSE; - } - - if(zend_hash_find(HASH_OF(array_ptr), var, var_len+5, (void **)&tmp) == SUCCESS) { - *return_value = **tmp; - zval_copy_ctor(return_value); - } else { - RETVAL_FALSE; - } -} -``` diff --git a/docs-old/streams.md b/docs-old/streams.md deleted file mode 100644 index 8220f9db78fc..000000000000 --- a/docs-old/streams.md +++ /dev/null @@ -1,405 +0,0 @@ -# An overview of the PHP streams abstraction - -WARNING: some prototypes in this file are out of date. - -## Why streams? - -You may have noticed a shed-load of issock parameters flying around the PHP -code; we don't want them - they are ugly and cumbersome and force you to special -case sockets and files every time you need to work with a "user-level" PHP file -pointer. - -Streams take care of that and present the PHP extension coder with an ANSI -stdio-alike API that looks much nicer and can be extended to support non file -based data sources. - -## Using streams - -Streams use a `php_stream*` parameter just as ANSI stdio (fread etc.) use a -`FILE*` parameter. - -The main functions are: - -```c -PHPAPI size_t php_stream_read(php_stream * stream, char * buf, size_t count); -PHPAPI size_t php_stream_write(php_stream * stream, const char * buf, size_t - count); -PHPAPI size_t php_stream_printf(php_stream * stream, - const char * fmt, ...); -PHPAPI int php_stream_eof(php_stream * stream); -PHPAPI int php_stream_getc(php_stream * stream); -PHPAPI char *php_stream_gets(php_stream * stream, char *buf, size_t maxlen); -PHPAPI int php_stream_close(php_stream * stream); -PHPAPI int php_stream_flush(php_stream * stream); -PHPAPI int php_stream_seek(php_stream * stream, off_t offset, int whence); -PHPAPI off_t php_stream_tell(php_stream * stream); -PHPAPI int php_stream_lock(php_stream * stream, int mode); -``` - -These (should) behave in the same way as the ANSI stdio functions with similar -names: fread, fwrite, fprintf, feof, fgetc, fgets, fclose, fflush, fseek, ftell, -flock. - -## Opening streams - -In most cases, you should use this API: - -```c -PHPAPI php_stream *php_stream_open_wrapper(const char *path, const char *mode, - int options, char **opened_path); -``` - -Where: - -* `path` is the file or resource to open. -* `mode` is the stdio compatible mode eg: "wb", "rb" etc. -* `options` is a combination of the following values: - * `IGNORE_PATH` (default) - don't use include path to search for the file - * `USE_PATH` - use include path to search for the file - * `IGNORE_URL` - do not use plugin wrappers - * `REPORT_ERRORS` - show errors in a standard format if something goes wrong. - * `STREAM_MUST_SEEK` - If you really need to be able to seek the stream and - don't need to be able to write to the original file/URL, use this option to - arrange for the stream to be copied (if needed) into a stream that can be - seek()ed. -* `opened_path` is used to return the path of the actual file opened, but if you - used `STREAM_MUST_SEEK`, may not be valid. You are responsible for - `efree()ing` `opened_path`. -* `opened_path` may be (and usually is) `NULL`. - -If you need to open a specific stream, or convert standard resources into -streams there are a range of functions to do this defined in `php_streams.h`. A -brief list of the most commonly used functions: - -```c -PHPAPI php_stream *php_stream_fopen_from_file(FILE *file, const char *mode); - /* Convert a FILE * into a stream. */ - -PHPAPI php_stream *php_stream_fopen_tmpfile(void); - /* Open a FILE * with tmpfile() and convert into a stream. */ - -PHPAPI php_stream *php_stream_fopen_temporary_file(const char *dir, - const char *pfx, char **opened_path); - /* Generate a temporary file name and open it. */ -``` - -There are some network enabled relatives in `php_network.h`: - -```c -PHPAPI php_stream *php_stream_sock_open_from_socket(int socket, int persistent); - /* Convert a socket into a stream. */ - -PHPAPI php_stream *php_stream_sock_open_host(const char *host, unsigned short port, - int socktype, int timeout, int persistent); - /* Open a connection to a host and return a stream. */ - -PHPAPI php_stream *php_stream_sock_open_unix(const char *path, int persistent, - struct timeval *timeout); - /* Open a UNIX domain socket. */ -``` - -## Stream utilities - -If you need to copy some data from one stream to another, you will be please to -know that the streams API provides a standard way to do this: - -```c -PHPAPI size_t php_stream_copy_to_stream(php_stream *src, - php_stream *dest, size_t maxlen); -``` - -If you want to copy all remaining data from the src stream, pass -`PHP_STREAM_COPY_ALL` as the maxlen parameter, otherwise maxlen indicates the -number of bytes to copy. This function will try to use mmap where available to -make the copying more efficient. - -If you want to read the contents of a stream into an allocated memory buffer, -you should use: - -```c -PHPAPI size_t php_stream_copy_to_mem(php_stream *src, char **buf, - size_t maxlen, int persistent); -``` - -This function will set buf to the address of the buffer that it allocated, which -will be maxlen bytes in length, or will be the entire length of the data -remaining on the stream if you set maxlen to `PHP_STREAM_COPY_ALL`. The buffer -is allocated using `pemalloc()`. You need to call `pefree()` to release the -memory when you are done. As with `copy_to_stream`, this function will try use -mmap where it can. - -If you have an existing stream and need to be able to `seek()` it, you can use -this function to copy the contents into a new stream that can be `seek()ed`: - -```c -PHPAPI int php_stream_make_seekable(php_stream *origstream, php_stream **newstream); -``` - -It returns one of the following values: - -```c -#define PHP_STREAM_UNCHANGED 0 /* orig stream was seekable anyway */ -#define PHP_STREAM_RELEASED 1 /* newstream should be used; origstream is no longer valid */ -#define PHP_STREAM_FAILED 2 /* an error occurred while attempting conversion */ -#define PHP_STREAM_CRITICAL 3 /* an error occurred; origstream is in an unknown state; you should close origstream */ -``` - -`make_seekable` will always set newstream to be the stream that is valid if the -function succeeds. When you have finished, remember to close the stream. - -NOTE: If you only need to seek forward, there is no need to call this function, -as the `php_stream_seek` can emulate forward seeking when the whence parameter -is `SEEK_CUR`. - -NOTE: Writing to the stream may not affect the original source, so it only makes -sense to use this for read-only use. - -NOTE: If the origstream is network based, this function will block until the -whole contents have been downloaded. - -NOTE: Never call this function with an origstream that is referenced as a -resource! It will close the origstream on success, and this can lead to a crash -when the resource is later used/released. - -NOTE: If you are opening a stream and need it to be seekable, use the -`STREAM_MUST_SEEK` option to php_stream_open_wrapper(); - -```c -PHPAPI int php_stream_supports_lock(php_stream * stream); -``` - -This function will return either 1 (success) or 0 (failure) indicating whether -or not a lock can be set on this stream. Typically, you can only set locks on -stdio streams. - -## Casting streams - -What if your extension needs to access the `FILE*` of a user level file pointer? -You need to "cast" the stream into a `FILE*`, and this is how you do it: - -```c -FILE * fp; -php_stream * stream; /* already opened */ - -if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void*)&fp, REPORT_ERRORS) == FAILURE) { - RETURN_FALSE; -} -``` - -The prototype is: - -```c -PHPAPI int php_stream_cast(php_stream * stream, int castas, void ** ret, int show_err); -``` - -The `show_err` parameter, if non-zero, will cause the function to display an -appropriate error message of type `E_WARNING` if the cast fails. - -`castas` can be one of the following values: - -```txt -PHP_STREAM_AS_STDIO - a stdio FILE* -PHP_STREAM_AS_FD - a generic file descriptor -PHP_STREAM_AS_SOCKETD - a socket descriptor -``` - -If you ask a socket stream for a `FILE*`, the abstraction will use fdopen to -create it for you. Be warned that doing so may cause buffered data to be lost -if you mix ANSI stdio calls on the FILE* with php stream calls on the stream. - -If your system has the fopencookie function, php streams can synthesize a -`FILE*` on top of any stream, which is useful for SSL sockets, memory based -streams, database streams etc. etc. - -In situations where this is not desirable, you should query the stream to see if -it naturally supports `FILE *`. You can use this code snippet for this purpose: - -```c -if (php_stream_is(stream, PHP_STREAM_IS_STDIO)) { - /* can safely cast to FILE* with no adverse side effects */ -} -``` - -You can use: - -```c -PHPAPI int php_stream_can_cast(php_stream * stream, int castas) -``` - -to find out if a stream can be cast, without actually performing the cast, so to -check if a stream is a socket you might use: - -```c -if (php_stream_can_cast(stream, PHP_STREAM_AS_SOCKETD) == SUCCESS) { - /* it can be a socket */ -} -``` - -Please note the difference between `php_stream_is` and `php_stream_can_cast`; -`stream_is` tells you if the stream is a particular type of stream, whereas -`can_cast` tells you if the stream can be forced into the form you request. The -former doesn't change anything, while the later *might* change some state in the -stream. - -## Stream internals - -There are two main structures associated with a stream - the `php_stream` -itself, which holds some state information (and possibly a buffer) and a -`php_stream_ops` structure, which holds the "virtual method table" for the -underlying implementation. - -The `php_streams` ops struct consists of pointers to methods that implement -read, write, close, flush, seek, gets and cast operations. Of these, an -implementation need only implement write, read, close and flush. The gets method -is intended to be used for streams if there is an underlying method that can -efficiently behave as fgets. The ops struct also contains a label for the -implementation that will be used when printing error messages - the stdio -implementation has a label of `STDIO` for example. - -The idea is that a stream implementation defines a `php_stream_ops` struct, and -associates it with a `php_stream` using `php_stream_alloc`. - -As an example, the `php_stream_fopen()` function looks like this: - -```c -PHPAPI php_stream * php_stream_fopen(const char * filename, const char * mode) -{ - FILE * fp = fopen(filename, mode); - php_stream * ret; - - if (fp) { - ret = php_stream_alloc(&php_stream_stdio_ops, fp, 0, 0, mode); - if (ret) - return ret; - - fclose(fp); - } - return NULL; -} -``` - -`php_stream_stdio_ops` is a `php_stream_ops` structure that can be used to -handle `FILE*` based streams. - -A socket based stream would use code similar to that above to create a stream to -be passed back to fopen_wrapper (or it's yet to be implemented successor). - -The prototype for php_stream_alloc is this: - -```c -PHPAPI php_stream * php_stream_alloc(php_stream_ops * ops, void * abstract, - size_t bufsize, int persistent, const char * mode) -``` - -* `ops` is a pointer to the implementation, -* `abstract` holds implementation specific data that is relevant to this - instance of the stream, -* `bufsize` is the size of the buffer to use - if 0, then buffering at the - stream -* `level` will be disabled (recommended for underlying sources that implement - their own buffering - such a `FILE*`) -* `persistent` controls how the memory is to be allocated - persistently so that - it lasts across requests, or non-persistently so that it is freed at the end - of a request (it uses pemalloc), -* `mode` is the stdio-like mode of operation - php streams places no real - meaning in the mode parameter, except that it checks for a `w` in the string - when attempting to write (this may change). - -The mode parameter is passed on to `fdopen/fopencookie` when the stream is cast -into a `FILE*`, so it should be compatible with the mode parameter of `fopen()`. - -## Writing your own stream implementation - -* **RULE #1**: when writing your own streams: make sure you have configured PHP - with `--enable-debug`. - Some great great pains have been taken to hook into the Zend memory manager to - help track down allocation problems. It will also help you spot incorrect use - of the STREAMS_DC, STREAMS_CC and the semi-private STREAMS_REL_CC macros for - function definitions. - -* RULE #2: Please use the stdio stream as a reference; it will help you - understand the semantics of the stream operations, and it will always be more - up to date than these docs :-) - -First, you need to figure out what data you need to associate with the -`php_stream`. For example, you might need a pointer to some memory for memory -based streams, or if you were making a stream to read data from an RDBMS like -MySQL, you might want to store the connection and rowset handles. - -The stream has a field called abstract that you can use to hold this data. If -you need to store more than a single field of data, define a structure to hold -it, allocate it (use pemalloc with the persistent flag set appropriately), and -use the abstract pointer to refer to it. - -For structured state you might have this: - -```c -struct my_state { - MYSQL conn; - MYSQL_RES * result; -}; - -struct my_state * state = pemalloc(sizeof(struct my_state), persistent); - -/* initialize the connection, and run a query, using the fields in state to - * hold the results */ - -state->result = mysql_use_result(&state->conn); - -/* now allocate the stream itself */ -stream = php_stream_alloc(&my_ops, state, 0, persistent, "r"); - -/* now stream->abstract == state */ -``` - -Once you have that part figured out, you can write your implementation and -define your own php_stream_ops struct (we called it my_ops in the above -example). - -For example, for reading from this weird MySQL stream: - -```c -static size_t php_mysqlop_read(php_stream * stream, char * buf, size_t count) -{ - struct my_state * state = (struct my_state*)stream->abstract; - - if (buf == NULL && count == 0) { - /* in this special case, php_streams is asking if we have reached the - * end of file */ - if (... at end of file ...) - return EOF; - else - return 0; - } - - /* pull out some data from the stream and put it in buf */ - ... mysql_fetch_row(state->result) ... - /* we could do something strange, like format the data as XML here, - and place that in the buf, but that brings in some complexities, - such as coping with a buffer size too small to hold the data, - so I won't even go in to how to do that here */ -} -``` - -Implement the other operations - remember that write, read, close and flush are -all mandatory. The rest are optional. Declare your stream ops struct: - -```c -php_stream_ops my_ops = { - php_mysqlop_write, php_mysqlop_read, php_mysqlop_close, - php_mysqlop_flush, NULL, NULL, NULL, - "Strange MySQL example" -} -``` - -That's it! - -Take a look at the STDIO implementation in streams.c for more information about -how these operations work. - -The main thing to remember is that in your close operation you need to release -and free the resources you allocated for the abstract field. In the case of the -example above, you need to use mysql_free_result on the rowset, close the -connection and then use pefree to dispose of the struct you allocated. You may -read the stream->persistent field to determine if your struct was allocated in -persistent mode or not. diff --git a/docs-old/unix-build-system.md b/docs-old/unix-build-system.md deleted file mode 100644 index 01bb8e4e51cb..000000000000 --- a/docs-old/unix-build-system.md +++ /dev/null @@ -1,102 +0,0 @@ -# PHP build system V5 overview - -* supports Makefile.ins during transition phase -* not-really-portable Makefile includes have been eliminated -* supports separate build directories without VPATH by using explicit rules only -* does not waste disk-space/CPU-time for building temporary libraries => - especially noticeable on slower systems -* slow recursive make replaced with one global Makefile -* eases integration of proper dependencies -* abandoning the "one library per directory" concept -* improved integration of the CLI -* several new targets: - * `build-modules`: builds and copies dynamic modules into `modules/` - * `install-cli`: installs the CLI only, so that the install-sapi target does - only what its name says -* finally abandoned automake -* changed some configure-time constructs to run at buildconf-time -* upgraded shtool to 1.5.4 -* removed `$(moduledir)` (use `EXTENSION_DIR`) - -## The reason for a new system - -It became more and more apparent that there is a severe need for addressing the -portability concerns and improving the chance that your build is correct (how -often have you been told to `make clean`? When this is done, you won't need to -anymore). - -## If you build PHP on a Unix system - -You, as a user of PHP, will notice no changes. Of course, the build system will -be faster, look better and work smarter. - -## If you are developing PHP - -### Extension developers - -Makefile.ins are abandoned. The files which are to be compiled are specified in -the `config.m4` now using the following macro: - -```m4 -PHP_NEW_EXTENSION([foo], [foo.c bar.c baz.cpp], [$ext_shared]) -``` - -E.g. this enables the extension foo which consists of three source-code modules, -two in C and one in C++. And, depending on the user's wishes, the extension will -even be built as a dynamic module. - -The full syntax: - -```m4 -PHP_NEW_EXTENSION(extname, sources [, shared [,sapi_class[, extra-cflags]]]) -``` - -Please have a look at `build/php.m4` for the gory details and meanings of the -other parameters. - -And that's basically it for the extension side. - -If you previously built sub-libraries for this module, add the source-code files -here as well. If you need to specify separate include directories, do it this -way: - -```m4 -PHP_NEW_EXTENSION([foo], [foo.c mylib/bar.c mylib/gregor.c],,, [-I@ext_srcdir@/lib]) -``` - -E.g. this builds the three files which are located relative to the extension -source directory and compiles all three files with the special include directive -(`@ext_srcdir@` is automatically replaced). - -Now, you need to tell the build system that you want to build files in a -directory called `$ext_builddir/lib`: - -```m4 -PHP_ADD_BUILD_DIR([$ext_builddir/lib]) -``` - -Make sure to call this after `PHP_NEW_EXTENSION`, because `$ext_builddir` is -only set by the latter. - -If you have a complex extension, you might to need add special Make rules. You -can do this by calling `PHP_ADD_MAKEFILE_FRAGMENT` in your `config.m4` after -`PHP_NEW_EXTENSION`. - -This will read a file in the source-dir of your extension called -`Makefile.frag`. In this file, `$(builddir)` and `$(srcdir)` will be replaced by -the values which are correct for your extension and which are again determined -by the `PHP_NEW_EXTENSION` macro. - -Make sure to prefix *all* relative paths correctly with either `$(builddir)` or -`$(srcdir)`. Because the build system does not change the working directory -anymore, we must use either absolute paths or relative ones to the top -build-directory. Correct prefixing ensures that. - -## General info - -The foundation for the new system is the flexible handling of sources and their -contexts. With the help of macros you can define special flags for each -source-file, where it is located, in which target context it can work, etc. - -Have a look at the well documented macros `PHP_ADD_SOURCES(_X)` in -`build/php.m4`. diff --git a/docs/Makefile b/docs/Makefile index 40f8dbfb5ed2..8c5314984947 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,30 +7,16 @@ SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build -RSTFMT = rstfmt -RSTFMTFLAGS = -w 100 - -rwildcard = $(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) -FILES = $(call rwildcard,$(SOURCEDIR),*.rst) all : html -.PHONY : check-formatting clean html preflight +.PHONY : clean html .SUFFIXES : # Disable legacy behavior -check-formatting : - $(RSTFMT) $(RSTFMTFLAGS) --check $(SOURCEDIR) - clean : - rm -rf -- $(wildcard $(SOURCEDIR)/.~ $(BUILDDIR)) + rm -rf -- $(BUILDDIR) -html : preflight +html : $(SPHINXBUILD) -M $@ $(SOURCEDIR) $(BUILDDIR) @printf 'Browse the \e]8;;%s\e\\%s\e]8;;\e\\.\n' \ "file://$(abspath $(BUILDDIR))/$@/index.$@" "php-src html docs locally" - -preflight : $(SOURCEDIR)/.~ - -$(SOURCEDIR)/.~ : $(FILES) - $(RSTFMT) $(RSTFMTFLAGS) $? - touch $@ diff --git a/docs/README.md b/docs/README.md index 3c0d7ddd2bcc..eca1fbebc214 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,12 +27,4 @@ your browser. ## Formatting -The files in this documentation are formatted using the -[``rstfmt``](https://github.com/dzhu/rstfmt) tool. - -```bash -rstfmt -w 100 source -``` - -This tool is not perfect. It breaks on custom directives, so we might switch to -either a fork or something else in the future. +Formatting is temporarily not enforced during the Markdown migration. diff --git a/docs/requirements.txt b/docs/requirements.txt index ca19fe15c2e0..71f8c72c9014 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ Sphinx +myst-parser>=5.1 sphinx-design sphinxawesome-theme -rstfmt diff --git a/docs/source/_templates/redirect.html b/docs/source/_templates/redirect.html new file mode 100644 index 000000000000..d5b1b908235e --- /dev/null +++ b/docs/source/_templates/redirect.html @@ -0,0 +1,18 @@ + + + + + + + Redirecting… + + + +

This page has moved to its new location.

+ + diff --git a/docs/source/conf.py b/docs/source/conf.py index f28102206a9f..90fdc2c97771 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,9 +15,18 @@ project = 'php-src docs' author = 'The PHP Group' extensions = [ + 'myst_parser', 'sphinx_design', 'sphinx.ext.autosectionlabel', ] +exclude_patterns = ['**/*TODO.md'] +myst_enable_extensions = [ + 'alert', + 'gfm_autolink', + 'strikethrough', + 'tasklist', +] +myst_heading_anchors = 6 templates_path = ['_templates'] html_theme = 'sphinxawesome_theme' html_static_path = ['_static'] @@ -58,3 +67,77 @@ ) html_theme_options = asdict(theme_options) pygments_style = 'sphinx' + + +_writing_tests_url = '../testing/writing-tests/index.html' +_writing_tests_sections_url = '../testing/writing-tests/sections/index.html' +_writing_tests_samples_url = '../testing/writing-tests/samples/index.html' + +_writing_tests_section_anchors = ''' +test description credits skipif conflicts whitespace-sensitive capture-stdio extensions post +post-raw put gzip-post deflate-post get cookie stdin ini args env phpdbg file fileeof file-external +redirecttest cgi xfail flaky expectheaders expect expect-external expectf expectf-external +expectregex expectregex-external clean +'''.split() + +_writing_tests_sample_anchors = ''' +capture-stdio-1-phpt capture-stdio-2-phpt capture-stdio-3-phpt clean-php conflicts-1-phpt +extensions-phpt file012-phpt phpdbg-1-phpt sample001-phpt sample002-phpt sample003-phpt +sample005-phpt sample006-phpt sample007-phpt sample008-phpt sample009-phpt sample010-phpt +sample011-phpt sample012-phpt sample013-phpt sample014-phpt sample016-phpt sample017-phpt +sample018-phpt sample019-phpt sample020-phpt sample021-phpt sample022-phpt sample023-phpt +sample024-phpt sample025-phpt sample026-phpt skipif2-phpt skipif-phpt xfailif-phpt +'''.split() + + +def _anchor_redirects(url, anchors): + return {f'#{anchor}': f'{url}#{anchor}' for anchor in anchors} + + +redirects = { + 'core/data-structures/reference-counting': { + 'redirect_url': '../memory-management/reference-counting.html', + }, + 'miscellaneous/running-tests': { + 'redirect_url': '../testing/running-tests/index.html', + }, + 'miscellaneous/writing-tests': { + 'redirect_url': _writing_tests_url, + 'redirect_anchors': { + **_anchor_redirects( + _writing_tests_sections_url, + _writing_tests_section_anchors, + ), + **_anchor_redirects( + _writing_tests_samples_url, + _writing_tests_sample_anchors, + ), + '#reference': _writing_tests_sections_url, + '#phpt-sections': _writing_tests_sections_url, + '#phpt-structure-details': _writing_tests_sections_url, + '#examples': _writing_tests_sections_url, + '#samples': _writing_tests_samples_url, + '#skipif-1': f'{_writing_tests_sections_url}#skipif', + '#extensions-1': f'{_writing_tests_sections_url}#extensions', + '#expectf-1': f'{_writing_tests_sections_url}#expectf', + '#expectregex-1': f'{_writing_tests_sections_url}#expectregex', + '#phpt-test-basics': _writing_tests_url, + '#writing-phpt-tests': _writing_tests_url, + '#basic-format': f'{_writing_tests_url}#minimal-test-layout', + '#analyzing-failing-tests': f'{_writing_tests_url}#analyzing-failures', + '#what-should-i-do-with-my-test-case-when-ive-written-and-tested-it': ( + _writing_tests_url + ), + '#writing-portable-php-tests': f'{_writing_tests_url}#portability', + }, + }, +} + + +def generate_redirects(_app): + for source, context in redirects.items(): + yield source, context, 'redirect.html' + + +def setup(app): + app.connect('html-collect-pages', generate_redirects) diff --git a/docs/source/core/TODO.md b/docs/source/core/TODO.md new file mode 100644 index 000000000000..9cf1af025b26 --- /dev/null +++ b/docs/source/core/TODO.md @@ -0,0 +1,8 @@ +Core TODO + +Pages to add: + +- Parser and AST: grammar generation, AST representation, compilation boundaries, + and important extension points. +- Virtual Machine: opcode execution, operands, call frames, and VM variants. +- Object Handlers: handler contracts, object storage, and common ownership traps. diff --git a/docs/source/core/data-structures/TODO.md b/docs/source/core/data-structures/TODO.md new file mode 100644 index 000000000000..b2ee1dfc2153 --- /dev/null +++ b/docs/source/core/data-structures/TODO.md @@ -0,0 +1,4 @@ +Data Structures TODO + +- Add a HashTable page covering ownership, iteration, mutation, and common APIs. +- Expand the zval macro table and document the remaining internal zval types. diff --git a/docs/source/core/data-structures/index.md b/docs/source/core/data-structures/index.md new file mode 100644 index 000000000000..9acfc453d928 --- /dev/null +++ b/docs/source/core/data-structures/index.md @@ -0,0 +1,11 @@ +# Data Structures + +```{toctree} + :hidden: + +zval +zend_string +zend_constant +``` + +This section provides an overview of the core data structures used in php-src. diff --git a/docs/source/core/data-structures/index.rst b/docs/source/core/data-structures/index.rst deleted file mode 100644 index 8fcb860f686b..000000000000 --- a/docs/source/core/data-structures/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -################# - Data structures -################# - -.. toctree:: - :hidden: - - zval - reference-counting - zend_string - zend_constant - -This section provides an overview of the core data structures used in php-src. diff --git a/docs/source/core/data-structures/zend_constant.md b/docs/source/core/data-structures/zend_constant.md new file mode 100644 index 000000000000..26489bc38d1e --- /dev/null +++ b/docs/source/core/data-structures/zend_constant.md @@ -0,0 +1,65 @@ +# zend_constant + +PHP constants (referring to non-class constants) are stored in a dedicated structure +`zend_constant`, which holds both the value of the constant and details for using it. + +## definition + +```c + + typedef struct _zend_constant { + zval value; + zend_string *name; + zend_string *filename; + HashTable *attributes; + } zend_constant; +``` + +The `value` field stores both the value itself and some metadata. The `name` and `filename` +store the name of the constant and the name of the file in which it was defined. The `attributes` +field stores the attributes applied to the constant. + +## value + +The value of the constant is stored in the {doc}`./zval` `value`. However, since the `zval` +structure has extra space, for constants this is used to store both the number of the module that +the constant was defined in, and a combination of the flags that affect the usage of the constant. + +This extra information is placed in the `uint32_t` field `value.u2.constant_flags`. + +The bottom 16 bits are used to hold flags about the constant + +```c + + #define CONST_PERSISTENT (1<<0) /* Persistent */ + #define CONST_NO_FILE_CACHE (1<<1) /* Can't be saved in file cache */ + #define CONST_DEPRECATED (1<<2) /* Deprecated */ + #define CONST_OWNED (1<<3) /* constant should be destroyed together + with class */ +``` + +These bottom 16 bits can be accessed with the `ZEND_CONSTANT_FLAGS()` macro, which is given a +`zend_constant` pointer as a parameter. + +On the other hand, the top 16 bits are used to store the number of the PHP module that registered +the constant. For constants defined by the user, the module number stored will be +`PHP_USER_CONSTANT`. This module number can be accessed with the `ZEND_CONSTANT_MODULE_NUMBER()` +macro, which is likewise given a `zend_constant` pointer as a parameter. + +## name + +The `name` holds a {doc}`zend_string` with the name of the constant, to allow searching for +constants that have already been defined. This string is released when the constant itself is freed. + +## filename + +The `filename` holds another `zend_string` with the name of the file in which the constant was +defined, or `NULL` if not defined userland code. This field provides the foundation for the PHP +method `ReflectionConstant::getFileName()`. + +## attributes + +The `attributes` holds a `HashTable` (essentially an array) with the details of the attributes +that were applied to the constant. Note that attributes can only be added to constants declared at +compile time via `const`, e.g. `const EXAMPLE = 123`, not those declared at runtime, e.g. +`define( 'EXAMPLE', 123 );`. diff --git a/docs/source/core/data-structures/zend_constant.rst b/docs/source/core/data-structures/zend_constant.rst deleted file mode 100644 index a5e85dc78638..000000000000 --- a/docs/source/core/data-structures/zend_constant.rst +++ /dev/null @@ -1,75 +0,0 @@ -############### - zend_constant -############### - -PHP constants (referring to non-class constants) are stored in a dedicated structure -``zend_constant``, which holds both the value of the constant and details for using it. - -************ - definition -************ - -.. code:: c - - typedef struct _zend_constant { - zval value; - zend_string *name; - zend_string *filename; - HashTable *attributes; - } zend_constant; - -The ``value`` field stores both the value itself and some metadata. The ``name`` and ``filename`` -store the name of the constant and the name of the file in which it was defined. The ``attributes`` -field stores the attributes applied to the constant. - -******* - value -******* - -The value of the constant is stored in the :doc:`./zval` ``value``. However, since the ``zval`` -structure has extra space, for constants this is used to store both the number of the module that -the constant was defined in, and a combination of the flags that affect the usage of the constant. - -This extra information is placed in the ``uint32_t`` field ``value.u2.constant_flags``. - -The bottom 16 bits are used to hold flags about the constant - -.. code:: c - - #define CONST_PERSISTENT (1<<0) /* Persistent */ - #define CONST_NO_FILE_CACHE (1<<1) /* Can't be saved in file cache */ - #define CONST_DEPRECATED (1<<2) /* Deprecated */ - #define CONST_OWNED (1<<3) /* constant should be destroyed together - with class */ - -These bottom 16 bits can be accessed with the ``ZEND_CONSTANT_FLAGS()`` macro, which is given a -``zend_constant`` pointer as a parameter. - -On the other hand, the top 16 bits are used to store the number of the PHP module that registered -the constant. For constants defined by the user, the module number stored will be -``PHP_USER_CONSTANT``. This module number can be accessed with the ``ZEND_CONSTANT_MODULE_NUMBER()`` -macro, which is likewise given a ``zend_constant`` pointer as a parameter. - -****** - name -****** - -The ``name`` holds a :doc:`zend_string` with the name of the constant, to allow searching for -constants that have already been defined. This string is released when the constant itself is freed. - -********** - filename -********** - -The ``filename`` holds another ``zend_string`` with the name of the file in which the constant was -defined, or ``NULL`` if not defined userland code. This field provides the foundation for the PHP -method ``ReflectionConstant::getFileName()``. - -************ - attributes -************ - -The ``attributes`` holds a ``HashTable`` (essentially an array) with the details of the attributes -that were applied to the constant. Note that attributes can only be added to constants declared at -compile time via ``const``, e.g. ``const EXAMPLE = 123``, not those declared at runtime, e.g. -``define( 'EXAMPLE', 123 );``. diff --git a/docs/source/core/data-structures/zend_string.md b/docs/source/core/data-structures/zend_string.md new file mode 100644 index 000000000000..59fc417be018 --- /dev/null +++ b/docs/source/core/data-structures/zend_string.md @@ -0,0 +1,196 @@ +# zend_string + +In C, strings are represented as sequential lists of characters, `char*` or `char[]`. The end of +the string is usually indicated by the special NUL character, `'\0'`. This comes with a few +significant downsides: + +- Calculating the length of the string is expensive, as it requires walking the entire string to + look for the terminating NUL character. +- The string may not contain the NUL character itself. +- It is easy to run into buffer overflows if the NUL byte is accidentally missing. + +php-src uses the `zend_string` struct as an abstraction over `char*`, which explicitly stores +the strings length, along with some other fields. It looks as follows: + +```c + + struct _zend_string { + zend_refcounted_h gc; + zend_ulong h; /* hash value */ + size_t len; + char val[1]; + }; +``` + +The `gc` field is used for {doc}`../memory-management/reference-counting`. The `h` field contains a hash value, +which is used for hash table lookups. The `len` field stores the length of the string in bytes, and +the `val` field contains the actual string data. + +You may wonder why the `val` field is declared as `char val[1]`. This is called the [struct +hack](https://www.geeksforgeeks.org/struct-hack/) in C. It is used to create structs with a flexible size, namely by allowing the last element +to be expanded arbitrarily. In this case, the size of `zend_string` depends on the string's +length, which is determined at runtime (see `_ZSTR_STRUCT_SIZE`). When allocating the string, we +append enough bytes to the allocation to hold the strings content. + +Here's a basic example of how to use `zend_string`: + +```c + + // Allocate the string. + zend_string *string = ZSTR_INIT_LITERAL("Hello world!", /* persistent */ false); + // Write it to the output buffer. + zend_write(ZSTR_VAL(string), ZSTR_LEN(string)); + // Decrease the reference count and free it if necessary. + zend_string_release(string); +``` + +`ZSTR_INIT_LITERAL` creates a `zend_string` from a string literal. It is just a wrapper around +`zend_string_init(char *string, size_t length, bool persistent)` that provides the length of the +string at compile time. The `persistent` parameter indicates whether the string is allocated using +`malloc` (`persistent == true`) or `emalloc`, PHP's custom allocator (`persistent == false`) that is emptied after each request. + +When you're done using the string, you must call `zend_string_release`, or the memory will leak. +`zend_string_release` will automatically call `malloc` or `emalloc`, depending on how the +string was allocated. After releasing the string, you must not access any of its fields anymore, as +it may have been freed if you were its last user. + +## API + +The string API is defined in `Zend/zend_string.h`. It provides a number of functions for creating +new strings. + +~~~{list-table} `zend_string` creation + :header-rows: 1 + + - - Function/Macro [^persistent] + - Description + + - - `ZSTR_INIT_LITERAL(s, p)` + - Creates a new string from a string literal. + + - - `zend_string_init(s, l, p)` + - Creates a new string from a character buffer. + + - - `zend_string_alloc(l, p)` + - Creates a new string of a given length without initializing its content. + + - - `zend_string_concat2(s1, l1, s2, l2)` + - Creates a non-persistent string by concatenating two character buffers. + + - - `zend_string_concat3(...)` + - Same as `zend_string_concat2`, but for three character buffers. + + - - `ZSTR_EMPTY_ALLOC()` + - Gets an immutable, empty string. This does not allocate memory. + + - - `ZSTR_CHAR(char)` + - Gets an immutable, single-character string. This does not allocate memory. + + - - `ZSTR_KNOWN(ZEND_STR_const)` + + - Gets an immutable, predefined string. Used for string common within PHP itself, e.g. + `"class"`. See `ZEND_KNOWN_STRINGS` in `Zend/zend_string.h`. This does not allocate + memory. + +~~~ + +[^persistent]: + + `s` = `zend_string`, `l` = `length`, `p` = `persistent`. + +As per php-src fashion, you are not supposed to access the `zend_string` fields directly. Instead, +use the following macros. There are macros for both `zend_string` and `zvals` known to contain +strings. + +```{list-table} Accessor macros + :header-rows: 1 + + - - `zend_string` + - `zval` + - Description + + - - `ZSTR_LEN` + - `Z_STRLEN[_P]` + - Returns the length of the string in bytes. + + - - `ZSTR_VAL` + - `Z_STRVAL[_P]` + - Returns the string data as a `char*`. + + - - `ZSTR_HASH` + - `Z_STRHASH[_P]` + - Computes the string hash if it hasn't already been, and returns it. + + - - `ZSTR_H` + - - + - Returns the string hash. This macro assumes that the hash has already been computed. + +``` + +```{list-table} Reference counting macros + :header-rows: 1 + + - - Macro + - Description + + - - `zend_string_copy(s)` + - Increases the reference count and returns the same string. The reference count is not + increased if the string is interned. + + - - `zend_string_release(s)` + - Decreases the reference count and frees the string if it goes to 0. + + - - `zend_string_dup(s, p)` + - Creates a true copy of the string in a new allocation, except if the string is interned. + + - - `zend_string_separate(s)` + - Duplicates the string if the reference count is greater than 1. See + {doc}`../memory-management/reference-counting` for details. + + - - `zend_string_realloc(s, l, p)` + + - Changes the size of the string. If the string has a reference count greater than 1 or if + the string is interned, a new string is created. You must always use the return value of + this function, as the original array may have been moved to a new location in memory. + +``` + +There are various functions to compare strings. The `zend_string_equals` function compares two +strings in full, while `zend_string_starts_with` checks whether the first argument starts with the +second. There are variations for `_ci` and `_literal`, i.e. case-insensitive comparison and +literal strings, respectively. We won't go over all variations here, as they are straightforward to +use. + +## Interned strings + +Programs use some strings many times. For example, if your program declares a class called +`MyClass`, it would be wasteful to allocate a new string `"MyClass"` every time it is referenced +within your program. Instead, when repeated strings are expected, php-src uses a technique called +string interning. Essentially, this is just a simple `HashTable` where existing interned strings are +stored. When creating a new interned string, php-src first checks the interned string buffer. If it +finds it there, it can return a pointer to the existing string. If it doesn't, it allocates a new +string and adds it to the buffer. + +```c + + zend_string *str1 = zend_new_interned_string( + ZSTR_INIT_LITERAL("MyClass", /* persistent */ false)); + + // In some other place entirely. + zend_string *str2 = zend_new_interned_string( + ZSTR_INIT_LITERAL("MyClass", /* persistent */ false)); + + assert(ZSTR_IS_INTERNED(str1)); + assert(ZSTR_IS_INTERNED(str2)); + assert(str1 == str2); +``` + +Interned strings are *not* reference counted, as they are expected to live for the entire request, +or longer. + +With opcache, this goes one step further by sharing strings across different processes. For example, +if you're using php-fpm with 8 workers, all workers will share the same interned strings buffer. It +gets a bit more complicated. During requests, no interned strings are actually created. Instead, +this is delayed until the script is persisted to shared memory. This means that +`zend_new_interned_string` may not actually return an interned string if opcache is enabled. +Usually you don't have to worry about this. diff --git a/docs/source/core/data-structures/zend_string.rst b/docs/source/core/data-structures/zend_string.rst deleted file mode 100644 index d6b20a49a74c..000000000000 --- a/docs/source/core/data-structures/zend_string.rst +++ /dev/null @@ -1,196 +0,0 @@ -############# - zend_string -############# - -In C, strings are represented as sequential lists of characters, ``char*`` or ``char[]``. The end of -the string is usually indicated by the special NUL character, ``'\0'``. This comes with a few -significant downsides: - -- Calculating the length of the string is expensive, as it requires walking the entire string to - look for the terminating NUL character. -- The string may not contain the NUL character itself. -- It is easy to run into buffer overflows if the NUL byte is accidentally missing. - -php-src uses the ``zend_string`` struct as an abstraction over ``char*``, which explicitly stores -the strings length, along with some other fields. It looks as follows: - -.. code:: c - - struct _zend_string { - zend_refcounted_h gc; - zend_ulong h; /* hash value */ - size_t len; - char val[1]; - }; - -The ``gc`` field is used for :doc:`./reference-counting`. The ``h`` field contains a hash value, -which is used for `hash table `__ lookups. The ``len`` field stores the length of the string -in bytes, and the ``val`` field contains the actual string data. - -You may wonder why the ``val`` field is declared as ``char val[1]``. This is called the `struct -hack`_ in C. It is used to create structs with a flexible size, namely by allowing the last element -to be expanded arbitrarily. In this case, the size of ``zend_string`` depends on the string's -length, which is determined at runtime (see ``_ZSTR_STRUCT_SIZE``). When allocating the string, we -append enough bytes to the allocation to hold the strings content. - -.. _struct hack: https://www.geeksforgeeks.org/struct-hack/ - -Here's a basic example of how to use ``zend_string``: - -.. code:: c - - // Allocate the string. - zend_string *string = ZSTR_INIT_LITERAL("Hello world!", /* persistent */ false); - // Write it to the output buffer. - zend_write(ZSTR_VAL(string), ZSTR_LEN(string)); - // Decrease the reference count and free it if necessary. - zend_string_release(string); - -``ZSTR_INIT_LITERAL`` creates a ``zend_string`` from a string literal. It is just a wrapper around -``zend_string_init(char *string, size_t length, bool persistent)`` that provides the length of the -string at compile time. The ``persistent`` parameter indicates whether the string is allocated using -``malloc`` (``persistent == true``) or ``emalloc``, `PHPs custom allocator `__ (``persistent -== false``) that is emptied after each request. - -When you're done using the string, you must call ``zend_string_release``, or the memory will leak. -``zend_string_release`` will automatically call ``malloc`` or ``emalloc``, depending on how the -string was allocated. After releasing the string, you must not access any of its fields anymore, as -it may have been freed if you were its last user. - -***** - API -***** - -The string API is defined in ``Zend/zend_string.h``. It provides a number of functions for creating -new strings. - -.. list-table:: ``zend_string`` creation - :header-rows: 1 - - - - Function/Macro [#persistent]_ - - Description - - - - ``ZSTR_INIT_LITERAL(s, p)`` - - Creates a new string from a string literal. - - - - ``zend_string_init(s, l, p)`` - - Creates a new string from a character buffer. - - - - ``zend_string_alloc(l, p)`` - - Creates a new string of a given length without initializing its content. - - - - ``zend_string_concat2(s1, l1, s2, l2)`` - - Creates a non-persistent string by concatenating two character buffers. - - - - ``zend_string_concat3(...)`` - - Same as ``zend_string_concat2``, but for three character buffers. - - - - ``ZSTR_EMPTY_ALLOC()`` - - Gets an immutable, empty string. This does not allocate memory. - - - - ``ZSTR_CHAR(char)`` - - Gets an immutable, single-character string. This does not allocate memory. - - - - ``ZSTR_KNOWN(ZEND_STR_const)`` - - - Gets an immutable, predefined string. Used for string common within PHP itself, e.g. - ``"class"``. See ``ZEND_KNOWN_STRINGS`` in ``Zend/zend_string.h``. This does not allocate - memory. - -.. [#persistent] - - ``s`` = ``zend_string``, ``l`` = ``length``, ``p`` = ``persistent``. - -As per php-src fashion, you are not supposed to access the ``zend_string`` fields directly. Instead, -use the following macros. There are macros for both ``zend_string`` and ``zvals`` known to contain -strings. - -.. list-table:: Accessor macros - :header-rows: 1 - - - - ``zend_string`` - - ``zval`` - - Description - - - - ``ZSTR_LEN`` - - ``Z_STRLEN[_P]`` - - Returns the length of the string in bytes. - - - - ``ZSTR_VAL`` - - ``Z_STRVAL[_P]`` - - Returns the string data as a ``char*``. - - - - ``ZSTR_HASH`` - - ``Z_STRHASH[_P]`` - - Computes the string hash if it hasn't already been, and returns it. - - - - ``ZSTR_H`` - - \- - - Returns the string hash. This macro assumes that the hash has already been computed. - -.. list-table:: Reference counting macros - :header-rows: 1 - - - - Macro - - Description - - - - ``zend_string_copy(s)`` - - Increases the reference count and returns the same string. The reference count is not - increased if the string is interned. - - - - ``zend_string_release(s)`` - - Decreases the reference count and frees the string if it goes to 0. - - - - ``zend_string_dup(s, p)`` - - Creates a true copy of the string in a new allocation, except if the string is interned. - - - - ``zend_string_separate(s)`` - - Duplicates the string if the reference count is greater than 1. See - :doc:`./reference-counting` for details. - - - - ``zend_string_realloc(s, l, p)`` - - - Changes the size of the string. If the string has a reference count greater than 1 or if - the string is interned, a new string is created. You must always use the return value of - this function, as the original array may have been moved to a new location in memory. - -There are various functions to compare strings. The ``zend_string_equals`` function compares two -strings in full, while ``zend_string_starts_with`` checks whether the first argument starts with the -second. There are variations for ``_ci`` and ``_literal``, i.e. case-insensitive comparison and -literal strings, respectively. We won't go over all variations here, as they are straightforward to -use. - -****************** - Interned strings -****************** - -Programs use some strings many times. For example, if your program declares a class called -``MyClass``, it would be wasteful to allocate a new string ``"MyClass"`` every time it is referenced -within your program. Instead, when repeated strings are expected, php-src uses a technique called -string interning. Essentially, this is just a simple `HashTable `__ where existing interned -strings are stored. When creating a new interned string, php-src first checks the interned string -buffer. If it finds it there, it can return a pointer to the existing string. If it doesn't, it -allocates a new string and adds it to the buffer. - -.. code:: c - - zend_string *str1 = zend_new_interned_string( - ZSTR_INIT_LITERAL("MyClass", /* persistent */ false)); - - // In some other place entirely. - zend_string *str2 = zend_new_interned_string( - ZSTR_INIT_LITERAL("MyClass", /* persistent */ false)); - - assert(ZSTR_IS_INTERNED(str1)); - assert(ZSTR_IS_INTERNED(str2)); - assert(str1 == str2); - -Interned strings are *not* reference counted, as they are expected to live for the entire request, -or longer. - -With opcache, this goes one step further by sharing strings across different processes. For example, -if you're using php-fpm with 8 workers, all workers will share the same interned strings buffer. It -gets a bit more complicated. During requests, no interned strings are actually created. Instead, -this is delayed until the script is persisted to shared memory. This means that -``zend_new_interned_string`` may not actually return an interned string if opcache is enabled. -Usually you don't have to worry about this. diff --git a/docs/source/core/data-structures/zval.md b/docs/source/core/data-structures/zval.md new file mode 100644 index 000000000000..cb02e9d79f56 --- /dev/null +++ b/docs/source/core/data-structures/zval.md @@ -0,0 +1,214 @@ +# zval + +PHP is a dynamic language. A variable can typically contain a value of any type, and the type of the +variable may even change during the execution of the program. Under the hood, this is implemented +through the `zval` struct. It is one of the most important data structures in php-src. It is +implemented as a "tagged union", meaning it stores what type of value it contains, and the value +itself. Let's look at the type first. + +## zval types + +```c + + #define IS_UNDEF 0 /* A variable that was never written to. */ + #define IS_NULL 1 + #define IS_FALSE 2 + #define IS_TRUE 3 + #define IS_LONG 4 /* An integer value. */ + #define IS_DOUBLE 5 /* A floating point value. */ + #define IS_STRING 6 + #define IS_ARRAY 7 + #define IS_OBJECT 8 + #define IS_RESOURCE 9 + #define IS_REFERENCE 10 +``` + +These simple integer constants determine what value is currently stored in a variable. If you are a +PHP developer, these types should sound fairly familiar. They are pretty much an exact reflection of +the types you may use in regular PHP code. One small oddity is that `IS_FALSE` and `IS_TRUE` are +implemented as separate types, instead of as a `IS_BOOL` type. + +Some of these types are self-contained, they don't store any auxiliary data. This includes +`IS_UNDEF`, `IS_NULL`, `IS_FALSE` and `IS_TRUE`. For the rest of the types, we are going to +require some additional memory to store the actual value of the variable. + +## zend_value + +```c + + typedef union _zend_value { + zend_long lval; /* long value, i.e. int. */ + double dval; /* double value, i.e. float. */ + zend_refcounted *counted; + zend_string *str; + zend_array *arr; + zend_object *obj; + zend_resource *res; + zend_reference *ref; + // Less important for now. + zend_ast_ref *ast; + zval *zv; + void *ptr; + zend_class_entry *ce; + zend_function *func; + struct { + uint32_t w1; + uint32_t w2; + } ww; + } zend_value; +``` + +A C union is a data type that may store any one of its members at a time, by being (at least) as big +as its biggest member. For example, `zend_value` may store the `lval` member, or the `dval` +member, but never both at the same time. However, it doesn't know which member is being stored. +Remembering this is our job, and that's exactly what the `IS_*` constants are for. + +The top members of `zend_value` mostly mirror the `IS_*` constants, with the exception of +`counted`. `counted` polymorphically refers to any [reference-counted](../memory-management/reference-counting.md) +value, including strings, arrays, objects, resources and references. `null` and `bool` are missing +from `zend_value` because their types are self-contained. + +The rest of the fields aren't important for now. + +## zval + +Together, the value and the tag make up the `zval`, along with some other fields. It may look +intimidating at first. We'll go over it step by step. + +```c + + typedef struct _zval_struct zval; + + struct _zval_struct { + zend_value value; + union { + uint32_t type_info; + struct { + ZEND_ENDIAN_LOHI_3( + uint8_t type, /* active type */ + uint8_t type_flags, + union { + uint16_t extra; /* not further specified */ + } u) + } v; + } u1; + union { + uint32_t next; /* hash collision chain */ + uint32_t cache_slot; /* cache slot (for RECV_INIT) */ + uint32_t opline_num; /* opline number (for FAST_CALL) */ + uint32_t lineno; /* line number (for ast nodes) */ + uint32_t num_args; /* arguments number for EX(This) */ + uint32_t fe_pos; /* foreach position */ + uint32_t fe_iter_idx; /* foreach iterator index */ + uint32_t guard; /* recursion and single property guard */ + uint32_t constant_flags; /* constant flags */ + uint32_t extra; /* not further specified */ + } u2; + }; +``` + +`zval.value` reserves space for the actual variable data, as discussed above. + +`zval.u1` stores the variable type, the given `IS_*` constant, along with some other flags. It's +definition looks a bit complicated. You can think of the entire field as a 4 byte integer, split +into 3 parts. `v.type` stores the actual variable type, `v.type_flags` is used for some +[reference-counting](../memory-management/reference-counting.md) flags, and `v.u.extra` is pretty +much unused. + +`zval.u2` defines some more storage for various contexts that is often unoccupied. It's there +because the memory would otherwise be wasted due to padding, so we may as well make use of it. We'll +go over the relevant ones in their corresponding chapters. + +## Macros + +The fields in `zval` should never be accessed directly. Instead, there are a plethora of macros to +access them, concealing some of the implementation details of the `zval` struct. For many macros, +there's a `_P`-suffixed variant that performs the same operation on a pointer to the given +`zval`. + +~~~{list-table} `zval` macros + :header-rows: 1 + + - - Macro + - Description + - - `Z_TYPE[_P]` + - Access the `zval.u1.v.type` part of the type flags, containing the `IS_*` type. + - - `Z_LVAL[_P]` + - Access the underlying `int` value. + - - `Z_DVAL[_P]` + - Access the underlying `float` value. + - - `Z_STR[_P]` + - Access the underlying `zend_string` pointer. + - - `Z_STRVAL[_P]` + - Access the strings raw `char *` pointer. + - - `Z_STRLEN[_P]` + - Access the strings length. + - - `ZVAL_COPY_VALUE(t, s)` + - Copy one `zval` to another, including type and value. + - - `ZVAL_COPY(t, s)` + - Same as `ZVAL_COPY_VALUE`, but if the value is reference counted, increase the counter. + +~~~ + +## Other zval types + +`zval`s are sometimes used internally with types that don't exist in userland. + +```c + + #define IS_CONSTANT_AST 11 + #define IS_INDIRECT 12 + #define IS_PTR 13 + #define IS_ALIAS_PTR 14 + #define _IS_ERROR 15 +``` + +`IS_CONSTANT_AST` is used to represent constant values (the right hand side of `const`, +property/parameter initializers, etc.) before they are evaluated. The evaluation of a constant +expression is not always possible during compilation, because they may contain references to values +only available at runtime. Until that evaluation is possible, the constants contain the AST of the +expression rather than the concrete values. When this flag is set, the `zval.value.ast` union member +is set accordingly. + +`IS_INDIRECT` indicates that the `zval.value.zv` member is populated. This field stores a +pointer to some other `zval`. This type is mainly used in two situations, namely for intermediate +values between `FETCH` and `ASSIGN` instructions, and for the sharing of variables in the symbol +table. + +`IS_PTR` is used for pointers to arbitrary data. Most commonly, this type is used internally for +`HashTable`, as `HashTable` may only store `zval` values. For example, `EG(class_table)` +represents the class table, which is a hash map of class names to the corresponding +`zend_class_entry`, representing the class. The same goes for functions and many other data types. +`IS_ALIAS_PTR` is used for class aliases registered via `class_alias`. Essentially, it just +allows differencing between members in the class table that are aliases, or actual classes. +Otherwise, it is essentially the same as `IS_PTR`. Arbitrary data is accessed through +`zval.value.ptr`, and casted to the correct type depending on context. If `ptr` stores a class +or function, the `zval.value.ce` or `zval.value.func` fields may be used, respectively. + +`_IS_ERROR` is used as an error value for some object handlers. + +```c + + /* Fake types used only for type hinting. + * These are allowed to overlap with the types below. */ + #define IS_CALLABLE 12 + #define IS_ITERABLE 13 + #define IS_VOID 14 + #define IS_STATIC 15 + #define IS_MIXED 16 + #define IS_NEVER 17 + + /* used for casts */ + #define _IS_BOOL 18 + #define _IS_NUMBER 19 +``` + +These flags are never actually stored in `zval.u1`. They are used for type hinting and in the +object handler API. + +This only leaves the `zval.value.ww` field. In short, this field is used on 32-bit platforms when +copying data from one `zval` to another. Normally, `zval.value.counted` is copied as a generic +value, no matter what the actual underlying type is. `zend_value` always consists of 8 bytes due +to the `double` field. Pointers, however, consist only of 4. Because we would otherwise miss the +other 4 bytes, they are copied manually using `z->value.ww.w2 = _w2;`. This happens in the +`ZVAL_COPY_VALUE_EX` macro, you won't ever have to care about this. diff --git a/docs/source/core/data-structures/zval.rst b/docs/source/core/data-structures/zval.rst deleted file mode 100644 index 512abfdf2195..000000000000 --- a/docs/source/core/data-structures/zval.rst +++ /dev/null @@ -1,225 +0,0 @@ -###### - zval -###### - -PHP is a dynamic language. A variable can typically contain a value of any type, and the type of the -variable may even change during the execution of the program. Under the hood, this is implemented -through the ``zval`` struct. It is one of the most important data structures in php-src. It is -implemented as a "tagged union", meaning it stores what type of value it contains, and the value -itself. Let's look at the type first. - -************ - zval types -************ - -.. code:: c - - #define IS_UNDEF 0 /* A variable that was never written to. */ - #define IS_NULL 1 - #define IS_FALSE 2 - #define IS_TRUE 3 - #define IS_LONG 4 /* An integer value. */ - #define IS_DOUBLE 5 /* A floating point value. */ - #define IS_STRING 6 - #define IS_ARRAY 7 - #define IS_OBJECT 8 - #define IS_RESOURCE 9 - #define IS_REFERENCE 10 - -These simple integer constants determine what value is currently stored in a variable. If you are a -PHP developer, these types should sound fairly familiar. They are pretty much an exact reflection of -the types you may use in regular PHP code. One small oddity is that ``IS_FALSE`` and ``IS_TRUE`` are -implemented as separate types, instead of as a ``IS_BOOL`` type. - -Some of these types are self-contained, they don't store any auxiliary data. This includes -``IS_UNDEF``, ``IS_NULL``, ``IS_FALSE`` and ``IS_TRUE``. For the rest of the types, we are going to -require some additional memory to store the actual value of the variable. - -************ - zend_value -************ - -.. code:: c - - typedef union _zend_value { - zend_long lval; /* long value, i.e. int. */ - double dval; /* double value, i.e. float. */ - zend_refcounted *counted; - zend_string *str; - zend_array *arr; - zend_object *obj; - zend_resource *res; - zend_reference *ref; - // Less important for now. - zend_ast_ref *ast; - zval *zv; - void *ptr; - zend_class_entry *ce; - zend_function *func; - struct { - uint32_t w1; - uint32_t w2; - } ww; - } zend_value; - -A C union is a data type that may store any one of its members at a time, by being (at least) as big -as its biggest member. For example, ``zend_value`` may store the ``lval`` member, or the ``dval`` -member, but never both at the same time. However, it doesn't know which member is being stored. -Remembering this is our job, and that's exactly what the ``IS_*`` constants are for. - -The top members of ``zend_value`` mostly mirror the ``IS_*`` constants, with the exception of -``counted``. ``counted`` polymorphically refers to any `reference counted `__ value, including -strings, arrays, objects, resources and references. ``null`` and ``bool`` are missing from -``zend_value`` because their types are self-contained. - -The rest of the fields aren't important for now. - -****** - zval -****** - -Together, the value and the tag make up the ``zval``, along with some other fields. It may look -intimidating at first. We'll go over it step by step. - -.. code:: c - - typedef struct _zval_struct zval; - - struct _zval_struct { - zend_value value; - union { - uint32_t type_info; - struct { - ZEND_ENDIAN_LOHI_3( - uint8_t type, /* active type */ - uint8_t type_flags, - union { - uint16_t extra; /* not further specified */ - } u) - } v; - } u1; - union { - uint32_t next; /* hash collision chain */ - uint32_t cache_slot; /* cache slot (for RECV_INIT) */ - uint32_t opline_num; /* opline number (for FAST_CALL) */ - uint32_t lineno; /* line number (for ast nodes) */ - uint32_t num_args; /* arguments number for EX(This) */ - uint32_t fe_pos; /* foreach position */ - uint32_t fe_iter_idx; /* foreach iterator index */ - uint32_t guard; /* recursion and single property guard */ - uint32_t constant_flags; /* constant flags */ - uint32_t extra; /* not further specified */ - } u2; - }; - -``zval.value`` reserves space for the actual variable data, as discussed above. - -``zval.u1`` stores the variable type, the given ``IS_*`` constant, along with some other flags. It's -definition looks a bit complicated. You can think of the entire field as a 4 byte integer, split -into 3 parts. ``v.type`` stores the actual variable type, ``v.type_flags`` is used for some -`reference counting `__ flags, and ``v.u.extra`` is pretty much unused. - -``zval.u2`` defines some more storage for various contexts that is often unoccupied. It's there -because the memory would otherwise be wasted due to padding, so we may as well make use of it. We'll -go over the relevant ones in their corresponding chapters. - -******** - Macros -******** - -The fields in ``zval`` should never be accessed directly. Instead, there are a plethora of macros to -access them, concealing some of the implementation details of the ``zval`` struct. For many macros, -there's a ``_P``-suffixed variant that performs the same operation on a pointer to the given -``zval``. - -.. list-table:: ``zval`` macros - :header-rows: 1 - - - - Macro - - Description - - - ``Z_TYPE[_P]`` - - Access the ``zval.u1.v.type`` part of the type flags, containing the ``IS_*`` type. - - - ``Z_LVAL[_P]`` - - Access the underlying ``int`` value. - - - ``Z_DVAL[_P]`` - - Access the underlying ``float`` value. - - - ``Z_STR[_P]`` - - Access the underlying ``zend_string`` pointer. - - - ``Z_STRVAL[_P]`` - - Access the strings raw ``char *`` pointer. - - - ``Z_STRLEN[_P]`` - - Access the strings length. - - - ``ZVAL_COPY_VALUE(t, s)`` - - Copy one ``zval`` to another, including type and value. - - - ``ZVAL_COPY(t, s)`` - - Same as ``ZVAL_COPY_VALUE``, but if the value is reference counted, increase the counter. - -.. - _todo: There are many more. - -****************** - Other zval types -****************** - -``zval``\ s are sometimes used internally with types that don't exist in userland. - -.. code:: c - - #define IS_CONSTANT_AST 11 - #define IS_INDIRECT 12 - #define IS_PTR 13 - #define IS_ALIAS_PTR 14 - #define _IS_ERROR 15 - -``IS_CONSTANT_AST`` is used to represent constant values (the right hand side of ``const``, -property/parameter initializers, etc.) before they are evaluated. The evaluation of a constant -expression is not always possible during compilation, because they may contain references to values -only available at runtime. Until that evaluation is possible, the constants contain the AST of the -expression rather than the concrete values. Check the `parser `__ chapter for more information -on ASTs. When this flag is set, the ``zval.value.ast`` union member is set accordingly. - -``IS_INDIRECT`` indicates that the ``zval.value.zv`` member is populated. This field stores a -pointer to some other ``zval``. This type is mainly used in two situations, namely for intermediate -values between ``FETCH`` and ``ASSIGN`` instructions, and for the sharing of variables in the symbol -table. - -.. - _todo: There are many more. - -``IS_PTR`` is used for pointers to arbitrary data. Most commonly, this type is used internally for -``HashTable``, as ``HashTable`` may only store ``zval`` values. For example, ``EG(class_table)`` -represents the class table, which is a hash map of class names to the corresponding -``zend_class_entry``, representing the class. The same goes for functions and many other data types. -``IS_ALIAS_PTR`` is used for class aliases registered via ``class_alias``. Essentially, it just -allows differencing between members in the class table that are aliases, or actual classes. -Otherwise, it is essentially the same as ``IS_PTR``. Arbitrary data is accessed through -``zval.value.ptr``, and casted to the correct type depending on context. If ``ptr`` stores a class -or function, the ``zval.value.ce`` or ``zval.value.func`` fields may be used, respectively. - -``_IS_ERROR`` is used as an error value for some `object handlers `__. It is described in more -detail in its own chapter. - -.. code:: c - - /* Fake types used only for type hinting. - * These are allowed to overlap with the types below. */ - #define IS_CALLABLE 12 - #define IS_ITERABLE 13 - #define IS_VOID 14 - #define IS_STATIC 15 - #define IS_MIXED 16 - #define IS_NEVER 17 - - /* used for casts */ - #define _IS_BOOL 18 - #define _IS_NUMBER 19 - -These flags are never actually stored in ``zval.u1``. They are used for type hinting and in the -`object handler `__ API. - -This only leaves the ``zval.value.ww`` field. In short, this field is used on 32-bit platforms when -copying data from one ``zval`` to another. Normally, ``zval.value.counted`` is copied as a generic -value, no matter what the actual underlying type is. ``zend_value`` always consists of 8 bytes due -to the ``double`` field. Pointers, however, consist only of 4. Because we would otherwise miss the -other 4 bytes, they are copied manually using ``z->value.ww.w2 = _w2;``. This happens in the -``ZVAL_COPY_VALUE_EX`` macro, you won't ever have to care about this. diff --git a/docs/source/core/memory-management/TODO.md b/docs/source/core/memory-management/TODO.md new file mode 100644 index 000000000000..869060e09a88 --- /dev/null +++ b/docs/source/core/memory-management/TODO.md @@ -0,0 +1,12 @@ +Memory Management TODO + +The Reference Counting page contained TODOs for dedicated Cycle Collector and +Zend Allocator pages; grouping them under Memory Management makes sense. Pages +to be added: + +- Cycle Collector: + candidate buffering, collection phases, collectable types, and correct use of + GC flags and macros. +- Zend Allocator: + allocator pairing, overflow-safe allocation, request/persistent lifetimes, and + relevant arena cleanup. diff --git a/docs/source/core/memory-management/index.md b/docs/source/core/memory-management/index.md new file mode 100644 index 000000000000..e818837dfef1 --- /dev/null +++ b/docs/source/core/memory-management/index.md @@ -0,0 +1,10 @@ +# Memory Management + +```{toctree} +--- +hidden: true +--- +reference-counting +``` + +This section describes how php-src manages the lifetime of allocated data. diff --git a/docs/source/core/data-structures/reference-counting.rst b/docs/source/core/memory-management/reference-counting.md similarity index 65% rename from docs/source/core/data-structures/reference-counting.rst rename to docs/source/core/memory-management/reference-counting.md index 6895d5d682a8..c2b35257a816 100644 --- a/docs/source/core/data-structures/reference-counting.rst +++ b/docs/source/core/memory-management/reference-counting.md @@ -1,9 +1,7 @@ -#################### - Reference counting -#################### +# Reference Counting In languages like C, when you need memory for storing data for an indefinite period of time or in a -large amount, you call ``malloc`` and ``free`` to acquire and release blocks of memory of some size. +large amount, you call `malloc` and `free` to acquire and release blocks of memory of some size. This sounds simple on the surface but turns out to be quite tricky, mainly because the data may not be freed for as long as it is used anywhere in the program. Sometimes this makes it unclear who is responsible for freeing the memory, and when to do so. Failure to handle this correctly may result @@ -17,12 +15,13 @@ used by another party. When the party no longer needs the value, it is responsib the reference count. Once the reference count reaches zero, we know the value is no longer needed anywhere, and that it may be freed. -.. code:: php +```php $a = new stdClass; // RC 1 $b = $a; // RC 2 unset($a); // RC 1 unset($b); // RC 0, free +``` Reference counting is needed for types that store auxiliary data, which are the following: @@ -33,13 +32,13 @@ Reference counting is needed for types that store auxiliary data, which are the - Resources These are either reference types (objects, references and resources) or they are large types that -don't fit in a single ``zend_value`` directly (strings, arrays). Simpler types either don't store a -value at all (``null``, ``false``, ``true``) or their value is small enough to fit directly in -``zend_value`` (``int``, ``float``). +don't fit in a single `zend_value` directly (strings, arrays). Simpler types either don't store a +value at all (`null`, `false`, `true`) or their value is small enough to fit directly in +`zend_value` (`int`, `float`). All of the reference counted types share a common initial struct sequence. -.. code:: c +```c typedef struct _zend_refcounted_h { uint32_t refcount; /* reference counter 32-bit */ @@ -57,79 +56,80 @@ All of the reference counted types share a common initial struct sequence. zend_refcounted_h gc; // ... }; +``` -The ``zend_refcounted_h`` struct is simple. It contains the reference count, and a ``type_info`` -field that repeats some of the type information that is also stored in the ``zval``, for situations -where we're not dealing with a ``zval`` directly. It also stores some additional fields, described -under `GC flags`_. +The `zend_refcounted_h` struct is simple. It contains the reference count, and a `type_info` +field that repeats some of the type information that is also stored in the `zval`, for situations +where we're not dealing with a `zval` directly. It also stores some additional fields, described +under [GC flags](#gc-flags). -******** - Macros -******** +## Macros -As with ``zval``, ``zend_refcounted_h`` members should not be accessed directly. Instead, you should +As with `zval`, `zend_refcounted_h` members should not be accessed directly. Instead, you should use the provided macros. There are macros that work with reference counted types directly, prefixed -with ``GC_``, or macros that work on ``zval`` values, usually prefixed with ``Z_``. Unfortunately, +with `GC_`, or macros that work on `zval` values, usually prefixed with `Z_`. Unfortunately, naming is not always consistent. -.. list-table:: ``zval`` macros +~~~{list-table} `zval` macros :header-rows: 1 - - Macro - - Non-RC [#non-rc]_ + - Non-RC [^non-rc] - Description - - - ``Z_REFCOUNT[_P]`` + - - `Z_REFCOUNT[_P]` - No - Returns the reference count. - - - ``Z_ADDREF[_P]`` + - - `Z_ADDREF[_P]` - No - Increases the reference count. - - - ``Z_TRY_ADDREF[_P]`` + - - `Z_TRY_ADDREF[_P]` - Yes - - Increases the reference count. May be called on any ``zval``. + - Increases the reference count. May be called on any `zval`. - - - ``zval_ptr_dtor`` + - - `zval_ptr_dtor` - Yes - Decreases the reference count and frees the value if the reference count reaches zero. -.. [#non-rc] +~~~ - Whether the macro works with non-reference counted types. If it does, the operation is usually a - no-op. If it does not, using the macro on these values is undefined behavior. +[^non-rc]: -.. list-table:: ``zend_refcounted_h`` macros + Whether the macro works with non-reference counted types. If it does, the operation is usually a + no-op. If it does not, using the macro on these values is undefined behavior. + +~~~{list-table} `zend_refcounted_h` macros :header-rows: 1 - - Macro - - Immutable [#immutable]_ + - Immutable [^immutable] - Description - - - ``GC_REFCOUNT[_P]`` + - - `GC_REFCOUNT[_P]` - Yes - Returns the reference count. - - - ``GC_ADDREF[_P]`` + - - `GC_ADDREF[_P]` - No - Increases the reference count. - - - ``GC_TRY_ADDREF[_P]`` + - - `GC_TRY_ADDREF[_P]` - Yes - Increases the reference count. - - - ``GC_DTOR[_P]`` + - - `GC_DTOR[_P]` - Yes - Decreases the reference count and frees the value if the reference count reaches zero. -.. [#immutable] +~~~ + +[^immutable]: - Whether the macro works with immutable types, described under `Immutable reference counted types`_. + Whether the macro works with immutable types, described under [Immutable reference counted types](#immutable-reference-counted-types). -************ - Separation -************ +## Separation PHP has value and reference types. Reference types are types that are shared through a reference, a "pointer" to the value, rather than the value itself. Modifying such a value in one place changes it @@ -143,17 +143,16 @@ the value is not observable from other places. Modifying a value with RC 1 is un we are the values sole owner. However, if the value has a reference count of >1, we need to create a fresh copy before modifying it. This process is called separation or CoW (copy on write). -.. code:: php +```php $a = [1, 2, 3]; // RC 1 $b = $a; // RC 2 $b[] = 4; // Separation, $a RC 1, $b RC 1 var_dump($a); // [1, 2, 3] var_dump($b); // [1, 2, 3, 4] +``` -*********************************** - Immutable reference counted types -*********************************** +## Immutable reference counted types Sometimes, even a reference counted type is not reference counted. When PHP runs in a multi-process or multi-threaded environment with opcache enabled, it shares some common values between processes @@ -161,22 +160,20 @@ or threads to reduce memory consumption. As you may know, sharing memory between threads can be tricky and requires special care when modifying values. In particular, modification usually requires exclusive access to the memory so that the other processes or threads wait until the value is done being updated. In this case, this synchronization is avoided by making the value -immutable and never modifying the reference count. Such values will receive the ``GC_IMMUTABLE`` -flag in their ``gc->u.type_info`` field. +immutable and never modifying the reference count. Such values will receive the `GC_IMMUTABLE` +flag in their `gc->u.type_info` field. -Some macros like ``GC_TRY_ADDREF`` will guard against immutable values. You should not use immutable -values on some macros, like ``GC_ADDREF``. This will result in undefined behavior, because the macro +Some macros like `GC_TRY_ADDREF` will guard against immutable values. You should not use immutable +values on some macros, like `GC_ADDREF`. This will result in undefined behavior, because the macro will not check whether the value is immutable before performing the reference count modifications. -You may execute PHP with the ``-d opcache.protect_memory=1`` flag to mark the shared memory as +You may execute PHP with the `-d opcache.protect_memory=1` flag to mark the shared memory as read-only and trigger a hardware exception if the code accidentally attempts to modify it. -***************** - Cycle collector -***************** +## Cycle collector Sometimes, reference counting is not enough. Consider the following example: -.. code:: php +```php $a = new stdClass; $b = new stdClass; @@ -184,21 +181,19 @@ Sometimes, reference counting is not enough. Consider the following example: $b->a = $a; unset($a); unset($b); +``` -When this code finishes, the reference count of both instances of ``stdClass`` will still be 1, as +When this code finishes, the reference count of both instances of `stdClass` will still be 1, as they reference each other. This is called a reference cycle. PHP implements a cycle collector that detects such cycles and frees values that are only reachable through their own references. The cycle collector will record values that may be involved in a cycle, and run when this buffer becomes full. It is also possible to invoke it explicitly by calling -the ``gc_collect_cycles()`` function. The cycle collectors design is described in the `Cycle -collector `_ chapter. +the `gc_collect_cycles()` function. -********** - GC flags -********** +## GC flags -.. code:: c +```c /* zval_gc_flags(zval.value->gc.u.type_info) (common flags) */ #define GC_NOT_COLLECTABLE (1<<4) @@ -206,23 +201,23 @@ collector `_ chapter. #define GC_IMMUTABLE (1<<6) /* can't be changed in place */ #define GC_PERSISTENT (1<<7) /* allocated using malloc */ #define GC_PERSISTENT_LOCAL (1<<8) /* persistent, but thread-local */ +``` -The ``GC_NOT_COLLECTABLE`` flag indicates that the value may not be involved in a reference cycle. +The `GC_NOT_COLLECTABLE` flag indicates that the value may not be involved in a reference cycle. This allows for a fast way to detect values that don't need to be added to the cycle collector buffer. Only arrays and objects may actually be involved in reference cycles. -The ``GC_PROTECTED`` flag is used to protect against recursion in various internal functions. For -example, ``var_dump`` recursively prints the contents of values, and marks visited values with the -``GC_PROTECTED`` flag. If the value is recursive, it prevents the same value from being visited +The `GC_PROTECTED` flag is used to protect against recursion in various internal functions. For +example, `var_dump` recursively prints the contents of values, and marks visited values with the +`GC_PROTECTED` flag. If the value is recursive, it prevents the same value from being visited again. -``GC_IMMUTABLE`` has been discussed in `Immutable reference counted types`_. +`GC_IMMUTABLE` has been discussed in [Immutable reference counted types](#immutable-reference-counted-types). -The ``GC_PERSISTENT`` flag indicates that the value was allocated using ``malloc``, instead of PHPs +The `GC_PERSISTENT` flag indicates that the value was allocated using `malloc`, instead of PHPs own allocator. Usually, such values are alive for the entire lifetime of the process, instead of -being freed at the end of the request. See the `Zend allocator `_ chapter for more -information. +being freed at the end of the request. -The ``GC_PERSISTENT_LOCAL`` flag indicates that a ``GC_PERSISTENT`` value is only accessible in one +The `GC_PERSISTENT_LOCAL` flag indicates that a `GC_PERSISTENT` value is only accessible in one thread, and is thus still safe to modify. This flag is only used in debug builds to satisfy an -``assert``. +`assert`. diff --git a/docs/source/core/output-buffering-TODO.md b/docs/source/core/output-buffering-TODO.md new file mode 100644 index 000000000000..4d262e775279 --- /dev/null +++ b/docs/source/core/output-buffering-TODO.md @@ -0,0 +1,7 @@ +## Open Questions + +- Should the userland API be adjusted and unified? + +Many bits of the manual (and very first implementation) do not comply with the +behaviour of the current (to be obsoleted) code, thus should the manual or the +behaviour be adjusted? diff --git a/docs-old/output-api.md b/docs/source/core/output-buffering.md similarity index 91% rename from docs-old/output-api.md rename to docs/source/core/output-buffering.md index 67bdfa3668dd..a56e731ef682 100644 --- a/docs-old/output-api.md +++ b/docs/source/core/output-buffering.md @@ -1,6 +1,6 @@ -# API adjustment to the old output control code +# Output Buffering -Everything now resides beneath the php_output namespace, and there's an API call +Everything resides beneath the `php_output` namespace, and there's an API call for every output handler op. Checking output control layers status: @@ -54,11 +54,11 @@ for every output handler op. // php_ob_end_buffers(0); php_output_discard_all(); - Stopping (and dropping) one output buffer: + Finalising and removing one output handler: // php_ob_end_buffer(1, 0) php_output_end(); - Stopping (and dropping) all output buffers: + Finalising and removing all output handlers: // php_ob_end_buffers(1, 0); php_output_end_all(); @@ -106,7 +106,7 @@ for every output handler op. // not possible with old API if ((flags & PHP_OUTPUT_HANDLER_CLEAN) && (flags & PHP_OUTPUT_HANDLER_FINAL)) { ... } -## Output handler hooks +## Output Handler Hooks The output handler can change its abilities at runtime. For example, the gz handler can remove the CLEANABLE and REMOVABLE bits when the first output has passed through it; @@ -126,11 +126,3 @@ context: nor removable PHP_OUTPUT_HANDLER_HOOK_DISABLE the second arg is ignored; marks the output handler as disabled - -## Open questions - -* Should the userland API be adjusted and unified? - -Many bits of the manual (and very first implementation) do not comply with the -behaviour of the current (to be obsoleted) code, thus should the manual or the -behaviour be adjusted? diff --git a/docs/source/core/streams/index.md b/docs/source/core/streams/index.md new file mode 100644 index 000000000000..b07b2b754969 --- /dev/null +++ b/docs/source/core/streams/index.md @@ -0,0 +1,220 @@ +# Streams + +PHP streams provide one byte-stream abstraction for files, sockets, memory and +wrapper-backed sources. Core code can therefore avoid source-specific I/O +paths. The stream layer also coordinates buffering, filters, contexts and +resource lifetime. + +## Basic Operations + +A `php_stream *` has a similar role to a `FILE *`. Normal callers use the API +in `main/php_streams.h` rather than accessing its fields. The main operations +are: + +```c +PHPAPI ssize_t php_stream_read( + php_stream *stream, char *buf, size_t count +); +PHPAPI ssize_t php_stream_write( + php_stream *stream, const char *buf, size_t count +); +PHPAPI ssize_t php_stream_printf( + php_stream *stream, const char *fmt, ... +); +PHPAPI bool php_stream_eof(php_stream *stream); +PHPAPI int php_stream_getc(php_stream *stream); +PHPAPI char *php_stream_get_line( + php_stream *stream, char *buf, size_t maxlen, size_t *returned_len +); +PHPAPI int php_stream_flush(php_stream *stream); +PHPAPI int php_stream_seek( + php_stream *stream, zend_off_t offset, int whence +); +PHPAPI zend_off_t php_stream_tell(const php_stream *stream); +#define php_stream_close(stream) \ + php_stream_free((stream), PHP_STREAM_FREE_CLOSE) +``` + +These mostly follow their stdio equivalents. Reads and writes return `ssize_t`, +so a negative result can report failure. Positions and offsets use +`zend_off_t`. + +Use these functions rather than calling `stream->ops` directly. The stream +layer maintains buffering, filters and its logical position around the +underlying operations. + +Use `php_stream_supports_lock()` before `php_stream_lock()` when locking is +required. Both delegate to the implementation's `set_option` callback. + +## Opening Streams + +Use `php_stream_open_wrapper()` for paths handled by stream wrappers: + +```c +zend_string *opened_path = NULL; +php_stream *stream = php_stream_open_wrapper( + path, mode, options, &opened_path +); +``` + +`options` is a bitmask. Common values are: + +- `USE_PATH`: search `PG(include_path)`. +- `IGNORE_URL`: disallow URL wrappers. +- `REPORT_ERRORS`: report failures through the stream error API. +- `STREAM_MUST_SEEK`: return a seekable stream or fail. +- `STREAM_WILL_CAST`: prepare a wrapper stream for a later cast. +- `STREAM_OPEN_PERSISTENT`: require a persistent stream. + +Pass `NULL` instead of `&opened_path` when the resolved path is not needed. +Otherwise, release a returned path with `zend_string_release()`. +`php_stream_open_wrapper_ex()` additionally accepts a `php_stream_context *`. + +Helpers for plain files, descriptors, pipes and temporary files are declared +in `main/streams/php_stream_plain_wrapper.h`. Socket helpers are declared in +`main/php_network.h`. + +## Copying Streams + +Use the following interfaces to copy between streams or into memory: + +```c +zend_result php_stream_copy_to_stream_ex( + php_stream *src, + php_stream *dest, + size_t maxlen, + size_t *copied +); + +zend_string *php_stream_copy_to_mem( + php_stream *src, + size_t maxlen, + bool persistent +); +``` + +Pass `PHP_STREAM_COPY_ALL` to copy until EOF. `copied` may be `NULL` when the +length is not needed. `php_stream_copy_to_stream()` is deprecated; use the +`_ex` form so failure is distinguishable from the byte count. + +Release a non-`NULL` string returned by `php_stream_copy_to_mem()` with +`zend_string_release()`. Its `persistent` argument controls the string's +allocation. + +## Seeking + +`php_stream_seek()` accounts for buffered data and can emulate a forward +`SEEK_CUR` by reading. Arbitrary seeks require the stream implementation to +provide a `seek` operation. + +`STREAM_MUST_SEEK` makes `php_stream_open_wrapper()` copy a non-seekable source +to a temporary stream. When this occurs, opening may block until the source is +exhausted, and writes to the temporary stream do not affect the source. + +`php_stream_make_seekable()` performs the same conversion explicitly: + +```c +php_stream *seekable; +php_stream_make_seekable_status status = php_stream_make_seekable( + stream, &seekable, PHP_STREAM_NO_PREFERENCE +); +``` + +Use `PHP_STREAM_PREFER_STDIO` to prefer a file-backed replacement, or +`PHP_STREAM_FORCE_CONVERSION` to replace an already seekable stream. + +- `PHP_STREAM_UNCHANGED`: the returned stream is the original stream. +- `PHP_STREAM_RELEASED`: the returned stream replaces the closed original. +- `PHP_STREAM_FAILED`: conversion failed and the original remains valid. +- `PHP_STREAM_CRITICAL`: conversion failed; close the original stream. + +After either success result, use the returned stream. + +> [!WARNING] +> Never call `php_stream_make_seekable()` for a stream referenced by a resource. +> It may close the original while the resource still points to it. + +## Casting Streams + +`php_stream_cast()` exposes a compatible underlying handle: + +```c +PHPAPI zend_result php_stream_cast( + php_stream *stream, + int castas, + void **result, + int show_err +); +``` + +The base cast types are: + +- `PHP_STREAM_AS_STDIO`: a `FILE *`. +- `PHP_STREAM_AS_FD`: a file descriptor. +- `PHP_STREAM_AS_SOCKETD`: a socket descriptor. +- `PHP_STREAM_AS_FD_FOR_SELECT`: a descriptor for `select()`. +- `PHP_STREAM_AS_FD_FOR_COPY`: a `php_io_fd` for internal copying. + +A non-zero `show_err` reports a warning when casting fails. +`php_stream_can_cast()` queries support by passing a `NULL` result, while +`php_stream_is()` only compares the operations table. + +Avoid `PHP_STREAM_CAST_TRY_HARD` unless consuming the source into a temporary +stream is acceptable. `PHP_STREAM_CAST_RELEASE` invalidates the stream after a +successful cast. + +Where supported, a stdio cast may create a `FILE *` with `fopencookie()` rather +than expose an existing handle. + +Do not interleave access through a cast handle with `php_stream_*()` calls. +Their separate buffering can desynchronise positions or lose buffered data. + +## Stream Implementations + +A `php_stream` holds common state and a `php_stream_ops` table. Implementations +store their own state in `stream->abstract` and allocate the stream with +`php_stream_alloc()`. + +For a normal data stream, `write`, `read`, `close` and `flush` are mandatory. +`seek`, `cast`, `stat` and `set_option` are optional: + +```c +static const php_stream_ops my_ops = { + my_write, + my_read, + my_close, + my_flush, + "my stream", + my_seek, + NULL, /* cast */ + NULL, /* stat */ + NULL, /* set_option */ +}; +``` + +The callbacks have several important contracts: + +- `read` and `write` return a byte count or a negative value on failure. +- `read` sets `stream->eof` when the source reaches its final EOF. +- `seek` writes the new position and returns zero on success. +- `close` releases owned state in `stream->abstract` and honours `close_handle` + for any underlying handle. + +Allocate a non-persistent stream as follows: + +```c +php_stream *stream = php_stream_alloc(&my_ops, state, NULL, mode); +``` + +The third argument is a persistent identifier, not a Boolean flag. When it is +non-`NULL`, owned implementation state must use matching persistent allocation. +Use `php_stream_is_persistent()` when freeing that state. Supply a valid +fopen-style `mode`; streams retain it for casts and other stream operations. + +Use `php_stream_to_zval()` when returning a stream as a PHP resource. Once +exposed, its resource controls the stream's lifetime. + +Build PHP with `--enable-debug` while developing an implementation. The +`STREAMS_*` call-site macros then help diagnose allocation and lifetime errors. +Current examples are available in `main/streams/plain_wrapper.c` and bundled +extensions such as `ext/bz2/bz2.c`. diff --git a/docs/source/extensions/bundled-extensions.md b/docs/source/extensions/bundled-extensions.md new file mode 100644 index 000000000000..f2d50d153eb4 --- /dev/null +++ b/docs/source/extensions/bundled-extensions.md @@ -0,0 +1,79 @@ +# Bundled Extensions + +```{toctree} + :hidden: + +bundled-extensions/filter +``` + +Bundled extensions are maintained in `ext/` as part of php-src. + +## Extension Developers + +The files which are to be compiled are specified in `config.m4` using the +following macro: + +```text +PHP_REQUIRE_CXX() +PHP_NEW_EXTENSION([foo], [foo.c bar.c baz.cpp], [$ext_shared],,, [cxx]) +``` + +E.g. this enables the extension foo which consists of three source-code modules, +two in C and one in C++. And, depending on the user's wishes, the extension will +even be built as a dynamic module. `PHP_REQUIRE_CXX` initialises the C++ +toolchain, and the `cxx` argument makes a shared extension use the C++ linker. + +The full syntax: + +```text +PHP_NEW_EXTENSION(extname, sources [, shared [, sapi_class [, extra-cflags [, cxx [, zend_ext]]]]]) +``` + +Please have a look at `build/php.m4` for the gory details and meanings of the +other parameters. + +And that's basically it for the extension side. + +If you would otherwise build sub-libraries for this module, add the source-code +files here as well. If you need to specify separate include directories, do it +this way: + +```text +PHP_NEW_EXTENSION([foo], [foo.c mylib/bar.c mylib/gregor.c],,, [-I@ext_srcdir@/lib]) +``` + +E.g. this builds the three files which are located relative to the extension +source directory and compiles all three files with the special include directive +(`@ext_srcdir@` is automatically replaced). + +Now, you need to tell the build system that you want to build files in a +directory called `$ext_builddir/lib`: + +```text +PHP_ADD_BUILD_DIR([$ext_builddir/lib]) +``` + +Make sure to call this after `PHP_NEW_EXTENSION`, because `$ext_builddir` is +only set by the latter. + +If you have a complex extension, you might need to add special Make rules. You +can do this by calling `PHP_ADD_MAKEFILE_FRAGMENT` in your `config.m4` after +`PHP_NEW_EXTENSION`. + +This will read a file in the source-dir of your extension called +`Makefile.frag`. In this file, `$(builddir)` and `$(srcdir)` will be replaced by +the values which are correct for your extension and which are again determined +by the `PHP_NEW_EXTENSION` macro. + +Make sure to prefix *all* relative paths correctly with either `$(builddir)` or +`$(srcdir)`. Because the build system does not change the working directory +anymore, we must use either absolute paths or relative ones to the top +build-directory. Correct prefixing ensures that. + +## General Info + +The foundation for the build system is the flexible handling of sources and their +contexts. With the help of macros you can define special flags for each +source-file, where it is located, in which target context it can work, etc. + +Have a look at the well documented `PHP_ADD_SOURCES` macro in `build/php.m4`. diff --git a/docs/source/extensions/bundled-extensions/filter.md b/docs/source/extensions/bundled-extensions/filter.md new file mode 100644 index 000000000000..4242faf02eab --- /dev/null +++ b/docs/source/extensions/bundled-extensions/filter.md @@ -0,0 +1,56 @@ +# ext/filter + +Input filter support is implemented as SAPI hook which is called before external +variables are registered. `ext/filter` uses the hook to store original input and +populate `PG(http_globals)`. + +## SAPI Input Filtering + +Callers pass decoded external vars through `sapi_module.input_filter`. The input +source is detected via `PARSE_POST`,`PARSE_GET`, `PARSE_COOKIE`, `PARSE_STRING`, +`PARSE_ENV` or `PARSE_SERVER`. First `php_default_input_filter()` is registered, +then `php_sapi_filter()` replaces it in `ext/filter` while module initialising. + +### Registration and Request Initialisation + +Declared in `main/SAPI.h`: + +```c +SAPI_API zend_result sapi_register_input_filter( + unsigned int (*input_filter)( + int arg, + const char *var, + char **val, + size_t val_len, + size_t *new_val_len + ), + unsigned int (*input_filter_init)(void) +); +``` + +If set, SAPI calls `input_filter_init` after activation for each request and +ignores its return value. + +Only one input-filter slot exists. Registration replaces both callbacks without +chaining them. Replacing `php_sapi_filter()` disables `ext/filter`'s raw-input +storage and registration in `PG(http_globals)`. + +### Callback Contract + +`SAPI_INPUT_FILTER_FUNC` in `main/SAPI.h` declares the same signature: + +- `arg`: input source. +- `var`: read-only variable name. +- `*val`: value, which may be modified or replaced. +- `val_len`: current value length. +- `new_val_len`: resulting length; may be `NULL`. + +A non-zero return tells most callers to register the modified variable. Zero +rejects it or indicates that the callback registered it. `php_sapi_filter()` +uses zero after registration in `PG(http_globals)`. + +`sapi_getenv()` ignores the return value and passes `NULL` for `new_val_len`. +Otherwise, set `new_val_len` before returning non-zero, even if unchanged. + +`*val` ownership varies by caller. Some `PARSE_SERVER` values are owned by the +SAPI, so callbacks must not free it unconditionally. diff --git a/docs-old/parameter-parsing-api.md b/docs/source/extensions/parameter-parsing.md similarity index 83% rename from docs-old/parameter-parsing-api.md rename to docs/source/extensions/parameter-parsing.md index fae10f2fec8a..6cc982cc79cc 100644 --- a/docs-old/parameter-parsing-api.md +++ b/docs/source/extensions/parameter-parsing.md @@ -1,4 +1,4 @@ -# Fast Parameter Parsing API +# Parameter Parsing In PHP 7, a "Fast Parameter Parsing API" was introduced. See [RFC](https://wiki.php.net/rfc/fast_zpp). @@ -6,7 +6,7 @@ In PHP 7, a "Fast Parameter Parsing API" was introduced. See This API uses inlining to improve applications performance compared with the `zend_parse_parameters()` function described below. -## Parameter parsing functions +## Parameter Parsing Functions Borrowing from Python's example, there is a set of functions that given the string of type specifiers, can parse the input parameters and store the results @@ -17,15 +17,14 @@ of parameters, and try to output meaningful error messages. ## Prototypes ```c -/* Implemented. */ -int zend_parse_parameters(int num_args, char *type_spec, ...); -int zend_parse_parameters_ex(int flags, int num_args, char *type_spec, ...); +zend_result zend_parse_parameters(uint32_t num_args, const char *type_spec, ...); +zend_result zend_parse_parameters_ex(int flags, uint32_t num_args, const char *type_spec, ...); ``` The `zend_parse_parameters()` function takes the number of parameters passed to the extension function, the type specifier string, and the list of pointers to -variables to store the results in. The _ex() version also takes 'flags' argument --- current only `ZEND_PARSE_PARAMS_QUIET` can be used as 'flags' to specify that +variables to store the results in. The `_ex()` version also takes a `flags` argument +-- currently only `ZEND_PARSE_PARAMS_QUIET` can be used as `flags` to specify that the function should operate quietly and not output any error messages. Both functions return `SUCCESS` or `FAILURE` depending on the result. @@ -33,30 +32,10 @@ Both functions return `SUCCESS` or `FAILURE` depending on the result. The auto-conversions are performed as necessary. Arrays, objects, and resources cannot be auto-converted. -PHP 5.3 includes a new function (actually implemented as macro): +The `zend_parse_parameters_none()` macro returns `SUCCESS` if no argument has +been passed to the function, `FAILURE` otherwise. -```c -int zend_parse_parameters_none(); -``` - -This returns `SUCCESS` if no argument has been passed to the function, `FAILURE` -otherwise. - -PHP 5.5 includes a new function: - -```c -int zend_parse_parameter(int flags, int arg_num, zval **arg, const char *spec, ...); -``` - -This function behaves like `zend_parse_parameters_ex()` except that instead of -reading the arguments from the stack, it receives a single zval to convert -(passed with double indirection). The passed zval may be changed in place as -part of the conversion process. - -See also -[Expose zend_parse_arg() as zend_parse_parameter()](https://wiki.php.net/rfc/zpp_improv#expose_zend_parse_arg_as_zend_parse_parameter). - -## Type specifiers +## Type Specifiers The following list shows the type specifier, its meaning, and the parameter types that need to be passed by address. All passed parameters are set if the PHP @@ -64,7 +43,7 @@ parameter is non-optional and untouched if optional and the parameter is not present. The only exception is O where the zend_class_entry* has to be provided on input and is used to verify the PHP parameter is an instance of that class. -```txt +```text a - array (zval*) A - array or object (zval*) b - boolean (bool) @@ -83,7 +62,7 @@ H - array or HASH_OF(object) (returned as HashTable*) l - long (zend_long) n - long or double (zval*) o - object of any type (zval*) -O - object of specific type given by class entry (zval*, zend_class_entry) +O - object of specific type given by class entry (zval*, zend_class_entry*) p - valid path (string without null bytes in the middle) and its length (char*, size_t) P - valid path (string without null bytes in the middle) as zend_string (zend_string*) r - resource (zval*) @@ -110,7 +89,7 @@ The following characters also have a meaning in the specifier string: has been provided and ``!ZEND_FCI_INITIALIZED(fci)`` to check if a PHP NULL is passed. -## Note on 64bit compatibility +## Note on 64-Bit Compatibility Please note that since version 7 PHP uses `zend_long` as integer type and `zend_string` with `size_t` as length, so make sure you pass `zend_long`s to "l" @@ -124,14 +103,14 @@ Both mistakes might cause memory corruptions and segfaults: ```c char *str; long str_len; /* XXX THIS IS WRONG!! Use size_t instead. */ -zend_parse_parameters(ZEND_NUM_ARGS(), "s", &str, &str_len) +zend_parse_parameters(ZEND_NUM_ARGS(), "s", &str, &str_len); ``` * 2 ```c int num; /* XXX THIS IS WRONG!! Use zend_long instead. */ -zend_parse_parameters(ZEND_NUM_ARGS(), "l", &num) +zend_parse_parameters(ZEND_NUM_ARGS(), "l", &num); ``` If you're in doubt, use check_parameters.php script to the parameters and their @@ -141,7 +120,7 @@ types (it can be found in `./scripts/dev/` directory of PHP sources): php ./scripts/dev/check_parameters.php /path/to/your/sources/ ``` -## Examples +## Parameter Parsing Examples ```c /* Gets a long, a string and its length, and a zval */ @@ -205,7 +184,7 @@ if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), /* Function that accepts only varargs (0 or more) */ -int i, num_varargs; +uint32_t i, num_varargs; zval *varargs = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &varargs, &num_varargs) == FAILURE) { @@ -216,15 +195,13 @@ for (i = 0; i < num_varargs; i++) { /* do something with varargs[i] */ } -if (varargs) { - efree(varargs); -} +/* varargs points into the call frame and must not be freed. */ /* Function that accepts a string, followed by varargs (1 or more) */ char *str; size_t str_len; -int i, num_varargs; +uint32_t i, num_varargs; zval *varargs = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s+", &str, &str_len, &varargs, &num_varargs) == FAILURE) { @@ -238,7 +215,7 @@ for (i = 0; i < num_varargs; i++) { /* Function that takes an array, followed by varargs, and ending with a long */ zend_long num; zval *array; -int i, num_varargs; +uint32_t i, num_varargs; zval *varargs = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "a*l", &array, &varargs, &num_varargs, &num) == FAILURE) { diff --git a/docs-old/self-contained-extensions.md b/docs/source/extensions/unbundled-extensions.md similarity index 50% rename from docs-old/self-contained-extensions.md rename to docs/source/extensions/unbundled-extensions.md index 522716c37089..64fdd90b5d20 100644 --- a/docs-old/self-contained-extensions.md +++ b/docs/source/extensions/unbundled-extensions.md @@ -1,40 +1,37 @@ -# How to create a self-contained PHP extension +# Unbundled Extensions -A self-contained extension can be distributed independently of the PHP source. -To create such an extension, two things are required: +An unbundled extension is maintained and distributed independently of php-src. +The PHP build system refers to these as self-contained extensions. To create +one, two things are required: -* Configuration file (config.m4) +* Configuration file (`config.m4`) * Source code for your module We will describe now how to create these and how to put things together. -## Preparing your system +## Preparing Your System -While the result will run on any system, a developer's setup needs these tools: +A developer's setup needs these tools in addition to a C compiler and `make`: -* GNU autoconf -* GNU m4 +* [GNU Autoconf](https://www.gnu.org/software/autoconf/) +* [GNU M4](https://www.gnu.org/software/m4/) -All of these are available from +## Converting an Existing Extension - ftp://ftp.gnu.org/pub/gnu/ - -## Converting an existing extension - -Just to show you how easy it is to create a self-contained extension, we will -convert an embedded extension into a self-contained one. Install PHP and execute -the following commands. +Just to show you how easy it is to create an unbundled extension, we will +convert a bundled extension into an unbundled one. Install PHP, including +its development tools and headers, and execute the following commands. ```bash mkdir /tmp/newext cd /tmp/newext ``` -You now have an empty directory. We will copy the files from the mysqli +You now have an empty directory. We will copy the files from the dl_test extension: ```bash -cp -rp php-src/ext/mysqli/* . +cp -R /path/to/php-src/ext/dl_test/. . ``` It is time to finish the module. Run: @@ -43,22 +40,19 @@ It is time to finish the module. Run: phpize ``` -You can now ship the contents of the directory - the extension can live -completely on its own. +The extension can now be built independently of the PHP source tree. The user instructions boil down to ```bash ./configure \ - [--with-php-config=/path/to/php-config] \ - [--with-mysqli=MYSQL-DIR] + [--with-php-config=/path/to/php-config] +make +make test make install ``` -The MySQL module will either use the embedded MySQL client library or the MySQL -installation in MYSQL-DIR. - -## Defining the new extension +## Defining the New Extension Our demo extension is called "foobar". @@ -68,15 +62,12 @@ header files, but that is not important here). The demo extension does not reference any external libraries (that is important, because the user does not need to specify anything). -`LTLIBRARY_SOURCES` specifies the names of the sources files. You can name an -arbitrary number of source files here. - -## Creating the M4 configuration file +## Creating the M4 Configuration File -The m4 configuration can perform additional checks. For a self-contained +The m4 configuration can perform additional checks. For an unbundled extension, you do not need more than a few macro calls. -```m4 +```text PHP_ARG_ENABLE([foobar], [whether to enable foobar], [AS_HELP_STRING([--enable-foobar], @@ -91,24 +82,30 @@ fi extension will be enabled by `PHP_NEW_EXTENSION` in shared mode. The first argument of `PHP_NEW_EXTENSION` describes the name of the extension. -The second names the source-code files. The third passes `$ext_shared` which is -set by `PHP_ARG_ENABLE/WITH` to `PHP_NEW_EXTENSION`. +The second names the source-code files. The third passes `$ext_shared`, which is +set by `PHP_ARG_ENABLE` or `PHP_ARG_WITH`, to `PHP_NEW_EXTENSION`. Please use always `PHP_ARG_ENABLE` or `PHP_ARG_WITH`. Even if you do not plan to distribute your module with PHP, these facilities allow you to integrate your module easily into the main PHP module framework. -## Create source files +## Creating Source Files + +`ext_skel.php` creates a current extension skeleton, including configuration, +source, stub and test files. From the root of the PHP source tree, run: + +```bash +php ext/ext_skel.php --ext foobar --vendor vendor_name +``` -`ext_skel.php` can be of great help when creating the common code for all -modules in PHP for you and also writing basic function definitions and C code -for handling arguments passed to your functions. See `./ext/ext_skel.php --help` -for further information. +The generated source includes basic function definitions and an example of +handling function arguments. See `php ext/ext_skel.php --help` for further +information. As for the rest, you are currently alone here. There are a lot of existing modules, use a simple module as a starting point and add your own code. -## Creating the self-contained extension +## Creating the Unbundled Extension Put `config.m4` and the source files into one directory. Then, run `phpize` (this is installed during `make install` by PHP). @@ -122,29 +119,31 @@ For example, if you configured PHP with `--prefix=/php`, you would run This will automatically copy the necessary build files and create configure from your `config.m4`. -And that's it. You now have a self-contained extension. +And that's it. You now have an unbundled extension. -## Installing a self-contained extension +## Installing an Unbundled Extension An extension can be installed by running: ```bash ./configure \ [--with-php-config=/path/to/php-config] +make +make test make install ``` -## Adding shared module support to a module +## Adding Shared Module Support to a Module -In order to be useful, a self-contained extension must be loadable as a shared +In order to be useful, an unbundled extension must be loadable as a shared module. The following will explain now how you can add shared module support to an existing module called `foo`. 1. In `config.m4`, use `PHP_ARG_WITH/PHP_ARG_ENABLE`. Then you will - automatically be able to use `--with-foo=shared[,..]` or - `--enable-foo=shared[,..]`. + automatically be able to use `--with-foo=shared[,DIR]` or + `--enable-foo=shared`. -2. In `config.m4`, use `PHP_NEW_EXTENSION([foo],.., [$ext_shared])` to enable +2. In `config.m4`, use `PHP_NEW_EXTENSION([foo], [foo.c], [$ext_shared])` to enable building the extension. 3. Add the following lines to your C source file: @@ -154,19 +153,3 @@ an existing module called `foo`. ZEND_GET_MODULE(foo) #endif ``` - -## PECL site conformity - -If you plan to release an extension to the PECL website, there are several -points to be regarded. - -1. Add `LICENSE` or `COPYING` to the `package.xml` - -2. The following should be defined in one of the extension header files - -```c -#define PHP_FOO_VERSION "1.2.3" -``` - -This macro has to be used within your foo_module_entry to indicate the -extension version. diff --git a/docs/source/index.rst b/docs/source/index.md similarity index 60% rename from docs/source/index.rst rename to docs/source/index.md index 21e2526f47f6..aa17bcf8a496 100644 --- a/docs/source/index.rst +++ b/docs/source/index.md @@ -1,38 +1,55 @@ -############## - php-src docs -############## +# php-src docs -.. toctree:: +```{toctree} :caption: Introduction :hidden: - introduction/high-level-overview - introduction/ides/index +introduction/high-level-overview +introduction/ides/index +``` -.. toctree:: +```{toctree} :caption: Core :hidden: - core/data-structures/index +core/data-structures/index +core/memory-management/index +core/output-buffering +core/streams/index +``` -.. toctree:: - :caption: Miscellaneous +```{toctree} + :caption: Extensions :hidden: - miscellaneous/stubs - miscellaneous/writing-tests - miscellaneous/running-tests +extensions/parameter-parsing +extensions/bundled-extensions +extensions/unbundled-extensions +``` -Welcome to the php-src documentation! +```{toctree} + :caption: Testing + :hidden: + +testing/running-tests/index +testing/writing-tests/index +``` -.. warning:: +```{toctree} + :caption: Miscellaneous + :hidden: - This documentation is work in progress. +miscellaneous/stubs +``` + +Welcome to the php-src documentation! - At this point in time, there are other guides that provide a more complete picture of the PHP - project. Check the `CONTRIBUTING.md - `__ file for a - list of technical resources. +> [!WARNING] +> This documentation is work in progress. +> +> At this point in time, there are other guides that provide a more complete picture of the PHP +> project. Check the [CONTRIBUTING.md](https://github.com/php/php-src/blob/master/CONTRIBUTING.md#technical-resources) +> file for a list of technical resources. php-src is the canonical implementation of the interpreter for the PHP programming language, as well as various extensions that provide common functionality. This documentation is intended to help you @@ -43,21 +60,17 @@ This documentation is not intended to be comprehensive, but is meant to explain are not easy to grasp by reading code alone. It describes best practices, and will frequently omit APIs that are discouraged for general use. -****************** - How to get help? -****************** +## How to get help? Getting started with a new and complicated project like php-src can be overwhelming. While there's no way around reading lots and lots of code, asking questions of somebody with experience can save a lot of time. Luckily, many core developers are eager to help. Here are some ways you can get in touch. -- `Discord `__ (``#php-internals`` channel) -- `R11 on StackOverflow `__ +- [Discord](https://phpc.chat) (`#php-internals` channel) +- [R11 on StackOverflow](https://chat.stackoverflow.com/rooms/11/php) -*************** - Prerequisites -*************** +## Prerequisites The php-src interpreter is written in C, and so are most of the bundled extensions. While extensions may also be written in C++, ext-intl is currently the only bundled extension to do so. It is diff --git a/docs/source/introduction/high-level-overview.rst b/docs/source/introduction/high-level-overview.md similarity index 74% rename from docs/source/introduction/high-level-overview.rst rename to docs/source/introduction/high-level-overview.md index 1240bed4c0e6..00ff6a12bf10 100644 --- a/docs/source/introduction/high-level-overview.rst +++ b/docs/source/introduction/high-level-overview.md @@ -1,6 +1,4 @@ -##################### - High-level overview -##################### +# High-level overview PHP is an interpreted language. Interpreted languages differ from compiled ones in that they aren't compiled into machine-readable code ahead of time. Instead, the source files are read, processed and @@ -9,9 +7,7 @@ prototyping, as it skips a lengthy compilation phase. However, it also poses som to performance, which is one of the primary reasons interpreters can be complex. php-src borrows many concepts from other compilers and interpreters. -********** - Pipeline -********** +## Pipeline The goal of the interpreter is to read the users source files, and to simulate the users intent. This process can be split into distinct phases that are easier to understand and implement. @@ -24,31 +20,31 @@ This process can be split into distinct phases that are easier to understand and php-src as a whole can be seen as a pipeline consisting of these stages, using the input of the previous phase and producing some output for the next. -.. code:: haskell +```haskell source_code |> tokenizer -- tokens |> parser -- ast |> compiler -- opcodes |> interpreter +``` Let's go into each phase in a bit more detail. -************** - Tokenization -************** +## Tokenization Tokenization, often called "lexing" or "scanning", is the process of taking an entire program file and splitting it into a list of words and symbols. Tokens generally consist of a type, a simple integer constant representing the token, and a lexeme, the literal string used in the source code. -.. code:: php +```php if ($cond) { echo "Cond is true\n"; } +``` -.. code:: text +```text T_IF "if" T_WHITESPACE " " @@ -64,27 +60,24 @@ integer constant representing the token, and a lexeme, the literal string used i ";" T_WHITESPACE "\n" "}" +``` -While tokenizers are not difficult to write by hand, PHP uses a tool called ``re2c`` to automate +While tokenizers are not difficult to write by hand, PHP uses a tool called `re2c` to automate this process. It takes a definition file and generates efficient C code to build these tokens from a -stream of characters. The definition for PHP lives in ``Zend/zend_language_scanner.l``. Check the -`re2c documentation`_ for details. +stream of characters. The definition for PHP lives in `Zend/zend_language_scanner.l`. Check the +[re2c documentation](https://re2c.org/) for details. -.. _re2c documentation: https://re2c.org/ - -********* - Parsing -********* +## Parsing Parsing is the process of reading the tokens generated from the tokenizer and building a tree structure from it. To humans, how source code elements are grouped seems obvious through whitespace -and the usage of symbols like ``()`` and ``{}``. However, computers cannot visually glance over the +and the usage of symbols like `()` and `{}`. However, computers cannot visually glance over the code to determine these boundaries quickly. To make it easier and faster to work with, we build a tree structure from the tokens to more closely reflect the source code the way humans see it. Here is a simplified example of what an AST from the tokens above might look like. -.. code:: text +```text ZEND_AST_IF { ZEND_AST_IF_ELEM { @@ -98,21 +91,16 @@ Here is a simplified example of what an AST from the tokens above might look lik }, }, } +``` Each AST node has a type and may have children. They also store their original position in the source code, and may define some arbitrary flags. These are omitted for brevity. -Like with tokenization, we use a tool called ``Bison`` to generate the parser implementation from a -grammar specification. The grammar lives in the ``Zend/zend_language_parser.y`` file. Check the -`Bison documentation`_ for details. Luckily, the syntax is quite approachable. - -.. _bison documentation: https://www.gnu.org/software/bison/manual/ - -Parsing is described in more detail in its `dedicated chapter `__. +Like with tokenization, we use a tool called `Bison` to generate the parser implementation from a +grammar specification. The grammar lives in the `Zend/zend_language_parser.y` file. Check the +[Bison documentation](https://www.gnu.org/software/bison/manual/) for details. Luckily, the syntax is quite approachable. -************* - Compilation -************* +## Compilation Computers don't understand human language, or even programming languages. They only understand machine code, which are sequences of simple, mostly atomic instructions for doing one thing. For @@ -130,65 +118,56 @@ in an actual CPU instruction set (e.g. adding two numbers), while others are muc With that little detour out of the way, the job of the compiler is to read the AST and translate it into our virtual machine instructions, also called opcodes. The code responsible for this -transformation lives in ``Zend/zend_compile.c``. It essentially traverses the AST and generates a +transformation lives in `Zend/zend_compile.c`. It essentially traverses the AST and generates a number of instructions, before going to the next node. Here's what the surprisingly compact opcodes for the AST above might look like: -.. code:: text +```text 0000 JMPZ CV0($cond) 0002 0001 ECHO string("Cond is true\n") 0002 RETURN int(1) +``` -**************** - Interpretation -**************** +## Interpretation -Finally, the opcodes are read and executed by the interpreter. PHPs uses `three-address code`_ for +Finally, the opcodes are read and executed by the interpreter. PHPs uses [three-address code](https://en.wikipedia.org/wiki/Three-address_code) for instructions. This essentially means that each instructions may have a result value, and at most two -operands. Most modern CPUs also use this format. Both result and operands in PHP are :doc:`zvals -<../core/data-structures/zval>`. - -.. _three-address code: https://en.wikipedia.org/wiki/Three-address_code +operands. Most modern CPUs also use this format. Both result and operands in PHP are {doc}`zvals <../core/data-structures/zval>`. How exactly each opcode behaves depends on its purpose. You can find a complete list of opcodes in -the generated ``Zend/zend_vm_opcodes.h`` file. The behavior of each instruction is defined in -``Zend/zend_vm_def.h``. +the generated `Zend/zend_vm_opcodes.h` file. The behavior of each instruction is defined in +`Zend/zend_vm_def.h`. Let's step through the opcodes form the example above: -- We start at the top, i.e. ``JMPZ``. If its first operand contains a "falsy" value, it will jump +- We start at the top, i.e. `JMPZ`. If its first operand contains a "falsy" value, it will jump to the instruction encoded in its second operand. If it is truthy, it will simply fall-through to the next instruction. +- The `ECHO` instruction prints its first operand. +- The `RETURN` operand terminates the current function. -- The ``ECHO`` instruction prints its first operand. - -- The ``RETURN`` operand terminates the current function. - -With these simple rules, we can see that the interpreter will ``echo`` only when ``$cond`` is -truthy, and skip over the ``echo`` otherwise. +With these simple rules, we can see that the interpreter will `echo` only when `$cond` is +truthy, and skip over the `echo` otherwise. That's it! This is how PHP works, fundamentally. Of course, we skipped over a ton of details. The VM -is quite complex, and will be discussed separately in the `virtual machine `__ chapter. +is quite complex. -********* - Opcache -********* +## Opcache As you may imagine, running this whole pipeline every time PHP serves a request is time consuming. Luckily, it is also not necessary. We can cache the opcodes in memory between requests, to skip over all of the phases, except for the execution phase. This is precisely what the opcache extension -does. It lives in the ``ext/opcache`` directory. +does. It lives in the `ext/opcache` directory. Opcache also performs some optimizations on the opcodes before caching them. As opcaches are expected to be reused many times, it is profitable to spend some additional time simplifying them if -possible to improve performance during execution. The optimizer lives in ``Zend/Optimizer``. +possible to improve performance during execution. The optimizer lives in `Zend/Optimizer`. -JIT -=== +### JIT The opcache also implements a JIT compiler, which stands for just-in-time compiler. This compiler takes the virtual PHP opcodes and turns it into actual machine instructions, with additional information gained at runtime. JITs are very complex pieces of software, so this book will likely -barely scratch the surface of how it works. It lives in ``ext/opcache/jit``. +barely scratch the surface of how it works. It lives in `ext/opcache/jit`. diff --git a/docs/source/introduction/ides/TODO.md b/docs/source/introduction/ides/TODO.md new file mode 100644 index 000000000000..510dbaa955f0 --- /dev/null +++ b/docs/source/introduction/ides/TODO.md @@ -0,0 +1,3 @@ +IDEs TODO + +- Add LLDB setup and debugging instructions, particularly for macOS. diff --git a/docs/source/introduction/ides/index.rst b/docs/source/introduction/ides/index.md similarity index 66% rename from docs/source/introduction/ides/index.rst rename to docs/source/introduction/ides/index.md index e12e0d5c7ccd..f2730674424a 100644 --- a/docs/source/introduction/ides/index.rst +++ b/docs/source/introduction/ides/index.md @@ -1,10 +1,9 @@ -###### - IDEs -###### +# IDEs -.. toctree:: +```{toctree} :hidden: - visual-studio-code +visual-studio-code +``` Here you can find instructions on how to effectively use common IDEs for php-src development. diff --git a/docs/source/introduction/ides/visual-studio-code.md b/docs/source/introduction/ides/visual-studio-code.md new file mode 100644 index 000000000000..731255bfd3d1 --- /dev/null +++ b/docs/source/introduction/ides/visual-studio-code.md @@ -0,0 +1,117 @@ +# Visual Studio Code + +> [!NOTE] +> These instructions have been tested on Linux. macOS should mostly work the same. For Windows, +> ymmv. + +An IDE can make navigating large code bases tremendously easier. Visual Studio Code is a popular and +free IDE that is well-suited for C development. It contains syntax highlighting, navigation, +auto-completion and a debugger. Check the [official website](https://code.visualstudio.com/) for +installation instructions. + +> [!NOTE] +> The `settings.json` file referenced below can be opened in the Settings page by pressing the +> "Open Settings (JSON)" button in the top right corner. Most of these settings can also be +> adjusted through the GUI. + +## C/C++ extension + +The [C/C++ extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) provides most of the features we'll need for php-src development. You can +find it in the extensions marketplace. You will also need `gcc` or `clang` installed. The +extension will mostly work out of the box, but it is advisable to use the `compile_commands.json` +file. It contains a list of all compiled files, along with the commands used to compile them. It +provides the extension with the necessary information about include paths and other compiler flags. + +To generate the `compile_commands.json` file, you can use the [compiledb](https://github.com/nickdiego/compiledb) tool. Install it using +`pip`, and then prefix your `make` command accordingly: + +```bash + + # Install compiledb + pip install compiledb + # Compile php-src and generate compile_commands.json + compiledb make -j8 +``` + +To tell the C/C++ extension to use the `compile_commands.json` file, add the following to your +`settings.json` file: + +```json + + { + "C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json" + } +``` + +## clangd + +The C/C++ extension usually works well enough. Some people find that `clangd` works better. +`clangd` is a language server built on top of the `clang` compiler. It only provides navigation +and code completion but no syntax highlighting and no debugger. As such, it should be used in +conjunction with the C/C++ extension. For the two extensions not to clash, add the following to your +`settings.json` file: + +```json + + { + "C_Cpp.intelliSenseEngine": "disabled" + } +``` + +Follow the [official installation instructions for clangd](https://clangd.llvm.org/installation.html), and then install the [clangd extension](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd). +Alternatively, you can let the extension install `clangd` for you. `clangd` requires a +`compile_commands.json` file, so make sure to follow the instructions from the previous section. +By default, `clangd` will auto-include header files on completion. php-src headers are somewhat +peculiar, so you might want to disable this option in your `settings.json` file: + +```json + + { + "clangd.arguments": [ + "-header-insertion=never" + ] + } +``` + +## gdb + +The C/C++ extension provides the ability to use Visual Studio Code as a frontend for `gdb`. Of +course, you will need `gdb` installed on your system, and php-src must be compiled with the +`--enable-debug` configure flag. Copy the following into your projects `.vscode/launch.json` +file: + +```json + + { + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/sapi/cli/php", + "args": [ + // Any options you want to test with + // "-dopcache.enable_cli=1", + "${relativeFile}", + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + // Useful if you build with --enable-address-sanitizer + "environment": [ + { "name": "USE_ZEND_ALLOC", "value": "0" }, + { "name": "USE_TRACKED_ALLOC", "value": "1" }, + { "name": "LSAN_OPTIONS", "value": "detect_leaks=0" }, + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { "text": "source ${workspaceFolder}/.gdbinit" }, + ] + } + ] + } +``` + +Set any breakpoint in your C code, open a `php` (or `phpt`) file and start debugging from the +"Run and Debug" tab in the sidebar. diff --git a/docs/source/introduction/ides/visual-studio-code.rst b/docs/source/introduction/ides/visual-studio-code.rst deleted file mode 100644 index 3493c00e83aa..000000000000 --- a/docs/source/introduction/ides/visual-studio-code.rst +++ /dev/null @@ -1,132 +0,0 @@ -#################### - Visual Studio Code -#################### - -.. note:: - - These instructions have been tested on Linux. macOS should mostly work the same. For Windows, - ymmv. - -An IDE can make navigating large code bases tremendously easier. Visual Studio Code is a popular and -free IDE that is well-suited for C development. It contains syntax highlighting, navigation, -auto-completion and a debugger. Check the `official website `__ for -installation instructions. - -.. note:: - - The ``settings.json`` file referenced below can be opened in the Settings page by pressing the - "Open Settings (JSON)" button in the top right corner. Most of these settings can also be - adjusted through the GUI. - -***************** - C/C++ extension -***************** - -The `C/C++ extension`_ provides most of the features we'll need for php-src development. You can -find it in the extensions marketplace. You will also need ``gcc`` or ``clang`` installed. The -extension will mostly work out of the box, but it is advisable to use the ``compile_commands.json`` -file. It contains a list of all compiled files, along with the commands used to compile them. It -provides the extension with the necessary information about include paths and other compiler flags. - -.. _c/c++ extension: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools - -To generate the ``compile_commands.json`` file, you can use the compiledb_ tool. Install it using -``pip``, and then prefix your ``make`` command accordingly: - -.. _compiledb: https://github.com/nickdiego/compiledb - -.. code:: bash - - # Install compiledb - pip install compiledb - # Compile php-src and generate compile_commands.json - compiledb make -j8 - -To tell the C/C++ extension to use the ``compile_commands.json`` file, add the following to your -``settings.json`` file: - -.. code:: json - - { - "C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json" - } - -******** - clangd -******** - -The C/C++ extension usually works well enough. Some people find that ``clangd`` works better. -``clangd`` is a language server built on top of the ``clang`` compiler. It only provides navigation -and code completion but no syntax highlighting and no debugger. As such, it should be used in -conjunction with the C/C++ extension. For the two extensions not to clash, add the following to your -``settings.json`` file: - -.. code:: json - - { - "C_Cpp.intelliSenseEngine": "disabled" - } - -Follow the `official installation instructions for clangd -`__, and then install the `clangd extension`_. -Alternatively, you can let the extension install ``clangd`` for you. ``clangd`` requires a -``compile_commands.json`` file, so make sure to follow the instructions from the previous section. -By default, ``clangd`` will auto-include header files on completion. php-src headers are somewhat -peculiar, so you might want to disable this option in your ``settings.json`` file: - -.. _clangd extension: https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd - -.. code:: json - - { - "clangd.arguments": [ - "-header-insertion=never" - ] - } - -***** - gdb -***** - -The C/C++ extension provides the ability to use Visual Studio Code as a frontend for ``gdb``. Of -course, you will need ``gdb`` installed on your system, and php-src must be compiled with the -``--enable-debug`` configure flag. Copy the following into your projects ``.vscode/launch.json`` -file: - -.. code:: json - - { - "version": "0.2.0", - "configurations": [ - { - "name": "(gdb) Launch", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/sapi/cli/php", - "args": [ - // Any options you want to test with - // "-dopcache.enable_cli=1", - "${relativeFile}", - ], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - // Useful if you build with --enable-address-sanitizer - "environment": [ - { "name": "USE_ZEND_ALLOC", "value": "0" }, - { "name": "USE_TRACKED_ALLOC", "value": "1" }, - { "name": "LSAN_OPTIONS", "value": "detect_leaks=0" }, - ], - "externalConsole": false, - "MIMode": "gdb", - "setupCommands": [ - { "text": "source ${workspaceFolder}/.gdbinit" }, - ] - } - ] - } - -Set any breakpoint in your C code, open a ``php`` (or ``phpt``) file and start debugging from the -"Run and Debug" tab in the sidebar. - -.. - _todo: lldb should work mostly the same, I believe. It's available by default on macOS, and as such might be more convenient. diff --git a/docs/source/miscellaneous/running-tests.rst b/docs/source/miscellaneous/running-tests.rst deleted file mode 100644 index bb2c56dcecd2..000000000000 --- a/docs/source/miscellaneous/running-tests.rst +++ /dev/null @@ -1,185 +0,0 @@ -############### - Running Tests -############### - -The easiest way to test your PHP build is to run make test from the command line after successfully -compiling. This will run the all tests for all enabled functionalities and extensions located in -tests folders under the source root directory using the PHP CLI binary. - -``make test`` executes the ``run-tests.php`` script under the source root (parallel builds will not -work). Therefore you can execute the script as follows: - -.. code:: shell - - sapi/cli/php [-c /path/to/php.ini] run-tests.php [ext/foo/tests/GLOB] - -****************************************** - Which php executable does make test use? -****************************************** - -If you are running the ``run-tests.php`` script from the command line (as above) you can set the -``TEST_PHP_EXECUTABLE`` environment variable to explicitly select the PHP executable that is to be -tested, that is, used to run the test scripts, otherwise it will use the PHP CLI binary that you -have compiled (``sapi/cli/php``). - -If you run the tests using make test, the PHP CLI and CGI executables are automatically set for you. -``make test`` executes ``run-tests.php`` script with the CLI binary. Some test scripts such as -session must be executed by CGI SAPI. Therefore, you must build PHP with CGI SAPI to perform all -tests. - -**Note:** The PHP binary executing ``run-tests.php`` and the PHP binary used for executing test -scripts may differ. If you use different PHP binary for executing ``run-tests.php`` script, you may -get errors. - -************************ - Which php.ini is used? -************************ - -``make test`` uses the same ``php.ini`` file as it would once installed. The tests have been written -to be independent of that ``php.ini`` file, so if you find a test that is affected by a setting, -please report this, so we can address the issue. - -********************************** - Which test scripts are executed? -********************************** - -The ``run-tests.php`` (``make test``), without any arguments executes all test scripts by extracting -all directories named tests from the source root and any subdirectories below. If there are files, -which have a phpt extension, ``run-tests.php`` looks at the sections in these files, determines -whether it should run it, by evaluating the ``SKIPIF`` section. If the test is eligible for -execution, the ``FILE`` section is extracted into a ``.php`` file (with the same name besides the -extension) and gets executed. When an argument is given or ``TESTS`` environment variable is set, -the GLOB is expanded by the shell and any file with extension ``*.phpt`` is regarded as a test file. - -Tester can easily execute tests selectively with as follows: - -.. code:: shell - - ./sapi/cli/php run-tests.php ext/mbstring/* - ./sapi/cli/php run-tests.php ext/mbstring/020.phpt - -********************* - Test Runner Options -********************* - -The ``run-tests.php`` test runner has many options. You can see these options by using the ``-h`` -option with ``run-tests.php``. - -You can set options by specifying them on the command line when you run ``php run-tests.php`` or if -you use ``make test`` through the ``TEST_PHP_ARGS`` environment variable: - -.. code:: shell - - php run-tests.php -j24 - # or - TEST_PHP_ARGS="-j24" make test - -Running Tests in Parallel -========================= - -The test runner can run tests in parallel, by using the ``-j`` option: - -.. code:: shell - - php run-tests.php -j24 ext/date/*.phpt - -************** - Test results -************** - -Test results are printed to standard output. If there is a failed test, the ``run-tests.php`` script -saves the result, the expected result and the code executed to the test script directory. For -example, if ``ext/myext/tests/myext.phpt`` fails to pass, the following files are created: - -- ``ext/myext/tests/myext.php`` - actual test file executed -- ``ext/myext/tests/myext.log`` - log of test execution (L) -- ``ext/myext/tests/myext.exp`` - expected output (E) -- ``ext/myext/tests/myext.out`` - output from test script (O) -- ``ext/myext/tests/myext.diff`` - diff of .out and .exp (D) - -Failed tests are always bugs. Either the test is bugged or not considering factors applying to the -tester's environment, or there is a bug in PHP. If this is a known bug, we strive to provide bug -numbers, in either the test name or the file name. You can check the status of such a bug, by going -to: ``https://bugs.php.net/12345`` where 12345 is the bug number. For clarity and automated -processing, bug numbers are prefixed by a hash sign '#' in test names and/or test cases are named -``bug12345.phpt``. - -**Note:** The files generated by tests can be selected by setting the environment variable -``TEST_PHP_LOG_FORMAT``. For each file you want to be generated use the character in brackets as -shown above (default is LEOD). The php file will be generated always. - -**Note**: You can set environment variable ``TEST_PHP_DETAILED`` to enable detailed test -information. - -******************* - Automated testing -******************* - -If you like to keep up to speed, with latest developments and quality assurance, setting the -environment variable ``NO_INTERACTION`` to 1, will not prompt the tester for any user input. - -Normally, the exit status of make test is zero, regardless of the results of independent tests. Set -the environment variable ``REPORT_EXIT_STATUS`` to ``1``, and make test will set the exit status -("$?") to non-zero, when an individual test has failed. - -Example script to be run by cron: - -.. code:: shell - - ========== qa-test.sh ============= - #!/bin/sh - - CO_DIR=$HOME/cvs/php7 - MYMAIL=qa-test@domain.com - TMPDIR=/var/tmp - TODAY=`date +"%Y%m%d"` - - # Make sure compilation environment is correct - CONFIGURE_OPTS='--disable-all --enable-cli --with-pcre' - export MAKE=gmake - export CC=gcc - - # Set test environment - export NO_INTERACTION=1 - export REPORT_EXIT_STATUS=1 - - cd $CO_DIR - cvs update . >>$TMPDIR/phpqatest.$TODAY - ./cvsclean ; ./buildconf ; ./configure $CONFIGURE_OPTS ; $MAKE - $MAKE test >>$TMPDIR/phpqatest.$TODAY 2>&1 - if test $? -gt 0 - then - cat $TMPDIR/phpqatest.$TODAY | mail -s"PHP-QA Test Failed for $TODAY" $MYMAIL - fi - ========== end of qa-test.sh ============= - -**Note:** The exit status of ``run-tests.php`` will be ``1`` when ``REPORT_EXIT_STATUS`` is set. The -result of make test may be higher than that. At present, gmake 3.79.1 returns 2, so it is advised to -test for non-zero, rather then a specific value. - -When ``make test`` finished running tests, and if there are any failed tests, the script asks to -send the logs to the PHP QA mailing list. Please answer ``y`` to this question so that we can -efficiently process the results, entering your e-mail address (which will not be transmitted in -plain text to any list) enables us to ask you some more information if a test failed. Note that this -script also uploads php -i output so your hostname may be transmitted. - -Specific tests can also be executed, like running tests for a certain extension. To do this you can -do like so (for example the standard library): - -.. code:: shell - - make test TESTS=ext/standard. - -Where ``TESTS=`` points to a directory containing .phpt files or a single .phpt file like: - -.. code:: shell - - make test TESTS=tests/basic/001.phpt. - -You can also pass options directly to the underlying script that runs the test suite -(``run-tests.phpt``) using ``TESTS=``, for example to check for memory leaks using Valgrind, the -``-m`` option can be passed along: ``make test TESTS="-m Zend/"``. For a full list of options that -can be passed along, then run ``make test TESTS=-h``. - -*Windows users:* On Windows the ``make`` command is called ``nmake`` instead of ``make``. This means -that on Windows you will have to run ``nmake test``, to run the test suite. diff --git a/docs/source/miscellaneous/stubs.rst b/docs/source/miscellaneous/stubs.md similarity index 68% rename from docs/source/miscellaneous/stubs.rst rename to docs/source/miscellaneous/stubs.md index 395034afe8d5..6f3f356479a7 100644 --- a/docs/source/miscellaneous/stubs.rst +++ b/docs/source/miscellaneous/stubs.md @@ -1,11 +1,9 @@ -####### - Stubs -####### +# Stubs Stub files are pieces of PHP code which only contain declarations. They do not include runnable code, but instead contain empty function and method bodies. A very basic stub looks like this: -.. code:: php +```php = 80000) # include "example_arginfo.h" #else # include "example_legacy_arginfo.h" #endif +``` -When ``@generate-legacy-arginfo`` is passed the minimum PHP version ID that needs to be supported, -then only one arginfo file is going to be generated, and ``#if`` preprocessor directives will ensure +When `@generate-legacy-arginfo` is passed the minimum PHP version ID that needs to be supported, +then only one arginfo file is going to be generated, and `#if` preprocessor directives will ensure compatibility with all the required PHP 8 versions. -PHP Version IDs are as follows: ``80000`` for PHP 8.0, ``80100`` for PHP PHP 8.1, ``80200`` for PHP -8.2, ``80300`` for PHP 8.3, and ``80400`` for PHP 8.4, +PHP Version IDs are as follows: `80000` for PHP 8.0, `80100` for PHP PHP 8.1, `80200` for PHP +8.2, `80300` for PHP 8.3, and `80400` for PHP 8.4, In this example we add a PHP 8.0 compatibility requirement to a slightly modified version of a previous example: -.. code:: php +```php = ...)`` conditions in the generated arginfo file: +Then notice the `#if (PHP_VERSION_ID >= ...)` conditions in the generated arginfo file: -.. code:: c +```c ... @@ -574,67 +575,66 @@ Then notice the ``#if (PHP_VERSION_ID >= ...)`` conditions in the generated argi return class_entry; } +``` -The preprocessor conditions are necessary because enumerations (``enum``), ``readonly`` properties, -and the ``not-serializable`` flag, are PHP 8.1 features and don't exist in PHP 8.0. +The preprocessor conditions are necessary because enumerations (`enum`), `readonly` properties, +and the `not-serializable` flag, are PHP 8.1 features and don't exist in PHP 8.0. -The registration of ``Number`` is therefore completely omitted, while the ``readonly`` flag is not -added for``Elephpant::$name`` for PHP versions before 8.1. +The registration of `Number` is therefore completely omitted, while the `readonly` flag is not +added for\`\`Elephpant::\$name\`\` for PHP versions before 8.1. Additionally, typed class constants are new in PHP 8.3, and hence a different registration function is used for versions before 8.3. -****************************************** - Generating Information for the Optimizer -****************************************** +## Generating Information for the Optimizer -A list of functions is maintained for the optimizer in ``Zend/Optimizer/zend_func_infos.h``. This +A list of functions is maintained for the optimizer in `Zend/Optimizer/zend_func_infos.h`. This file contains extra information about the return type and the cardinality of the return value. This can enable more accurate optimizations (i.e. better type inference). -Previously, the file was maintained manually, but since PHP 8.1, ``gen_stub.php`` can take care of -this with the ``--generate-optimizer-info`` option. +Previously, the file was maintained manually, but since PHP 8.1, `gen_stub.php` can take care of +this with the `--generate-optimizer-info` option. This feature is only available for built-in stubs inside php-src, since currently there is no way to -provide the function list for the optimizer other than overwriting ``zend_func_infos.h`` directly. +provide the function list for the optimizer other than overwriting `zend_func_infos.h` directly. -A function is added to ``zend_func_infos.h`` if either the ``@return`` or the ``@refcount`` PHPDoc +A function is added to `zend_func_infos.h` if either the `@return` or the `@refcount` PHPDoc tag supplies more information than what is available based on the return type declaration. By -default, scalar return types have a ``refcount`` of ``0``, while non-scalar values are ``N``. If a -function can only return newly created non-scalar values, its ``refcount`` can be set to ``1``. +default, scalar return types have a `refcount` of `0`, while non-scalar values are `N`. If a +function can only return newly created non-scalar values, its `refcount` can be set to `1`. An example from the built-in functions: -.. code:: php +```php /** * @return array * @refcount 1 */ function get_declared_classes(): array {} +``` Functions can be evaluated at compile-time if their arguments are known in compile-time, and their behavior is free from side-effects and is not affected by the global state. The list of such functions in the optimizer was maintained manually until PHP 8.2. -Since PHP 8.2, the ``@compile-time-eval`` PHPDoc tag can be applied to any function which conforms +Since PHP 8.2, the `@compile-time-eval` PHPDoc tag can be applied to any function which conforms to the above restrictions in order for them to qualify as evaluable at compile-time. The feature -internally works by adding the ``ZEND_ACC_COMPILE_TIME_EVAL`` function flag. +internally works by adding the `ZEND_ACC_COMPILE_TIME_EVAL` function flag. In PHP 8.4, arity-based frameless functions were introduced. This is another optimization technique, which results in faster internal function calls by eliminating unnecessary checks for the number of passed parameters—if the number of passed arguments is known at compile-time. -To take advantage of frameless functions, add the ``@frameless-function`` PHPDoc tag with some +To take advantage of frameless functions, add the `@frameless-function` PHPDoc tag with some configuration. -Since only arity-based optimizations are supported, the tag has the form: ``@frameless-function -{"arity": NUM}``. ``NUM`` is the number of parameters for which a frameless function is available. +Since only arity-based optimizations are supported, the tag has the form: `@frameless-function {"arity": NUM}`. `NUM` is the number of parameters for which a frameless function is available. -The stub of ``in_array()`` is a good example: +The stub of `in_array()` is a good example: -.. code:: php +```php /** * @compile-time-eval @@ -642,11 +642,12 @@ The stub of ``in_array()`` is a good example: * @frameless-function {"arity": 3} */ function in_array(mixed $needle, array $haystack, bool $strict = false): bool {} +``` Apart from being compile-time evaluable, it has a frameless function counterpart for both the 2 and the 3-parameter signatures: -.. code:: c +```c /* The regular in_array() function */ PHP_FUNCTION(in_array) @@ -681,59 +682,55 @@ the 3-parameter signatures: flf_clean:; } +``` -************************************** - Generating Signatures for the Manual -************************************** +## Generating Signatures for the Manual The manual should reflect the exact same signatures which are represented by the stubs. This is not -exactly the case yet for built-in symbols, but ``gen_stub.php`` has multiple features to automate +exactly the case yet for built-in symbols, but `gen_stub.php` has multiple features to automate the process of synchronization. -Newly added functions or methods can be documented by providing the ``--generate-methodsynopses`` +Newly added functions or methods can be documented by providing the `--generate-methodsynopses` option. -Running ``./build/gen_stub.php --generate-methodsynopses ./ext/mbstring -../doc-en/reference/mbstring`` will create a dedicated page for each ``ext/mbstring`` function which -is not yet documented, and saves them into the ``../doc-en/reference/mbstring/functions`` directory. +Running `./build/gen_stub.php --generate-methodsynopses ./ext/mbstring ../doc-en/reference/mbstring` will create a dedicated page for each `ext/mbstring` function which +is not yet documented, and saves them into the `../doc-en/reference/mbstring/functions` directory. Since these are stub documentation pages, many of the sections are empty. Relevant descriptions have to be added, and irrelevant sections should be removed. Functions or methods that are already available in the manual, the documented signatures can be -updated by providing the ``--replace-methodsynopses`` option. +updated by providing the `--replace-methodsynopses` option. -Running ``./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/`` will update the function or +Running `./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/` will update the function or method signatures in the English documentation whose stub counterpart is found. -Class signatures can be updated in the manual by providing the ``--replace-classsynopses`` option. +Class signatures can be updated in the manual by providing the `--replace-classsynopses` option. -Running ``./build/gen_stub.php --replace-classsynopses ./ ../doc-en/`` will update all the class +Running `./build/gen_stub.php --replace-classsynopses ./ ../doc-en/` will update all the class signatures in the English documentation whose stub counterpart is found. -If a symbol is not intended to be documented, the ``@undocumentable`` PHPDoc tag should be added to +If a symbol is not intended to be documented, the `@undocumentable` PHPDoc tag should be added to it. Doing so will prevent any documentation to be created for the given symbol. To avoid a whole stub file to be added to the manual, this PHPDoc tag should be applied to the file itself. These flags are useful for symbols which exist only for testing purposes (e.g. the ones declared for -``ext/zend_test``), or by some other reason documentation is not possible. +`ext/zend_test`), or by some other reason documentation is not possible. -************ - Validation -************ +## Validation -You can use the ``--verify`` flag to ``gen_stub.php`` to validate whether the alias function/method +You can use the `--verify` flag to `gen_stub.php` to validate whether the alias function/method signatures are correct. An alias function/method should have the exact same signature as its aliased function/method -counterpart, apart from the name. In some cases this is not possible. For example. ``bzwrite()`` is -an alias of ``fwrite()``, but the name of the first parameter is different because the resource +counterpart, apart from the name. In some cases this is not possible. For example. `bzwrite()` is +an alias of `fwrite()`, but the name of the first parameter is different because the resource types differ. -In order to suppress the error when the check is false positive, the ``@no-verify`` PHPDoc tag +In order to suppress the error when the check is false positive, the `@no-verify` PHPDoc tag should be applied to the alias: -.. code:: php +```php /** * @param resource $bz @@ -741,13 +738,13 @@ should be applied to the alias: * @no-verify Uses different parameter name */ function bzwrite($bz, string $data, ?int $length = null): int|false {} +``` Besides aliases, the contents of the documentation can also be validated by providing the -``--verify-manual`` option to ``gen_stub.php``. This flag requires the directory with the source -stubs, and the target manual directory, as in ``./build/gen_stub.php --verify-manual ./ -../doc-en/``. +`--verify-manual` option to `gen_stub.php`. This flag requires the directory with the source +stubs, and the target manual directory, as in `./build/gen_stub.php --verify-manual ./ ../doc-en/`. -For this validation, all ``php-src`` stubs and the full English documentation should be available by +For this validation, all `php-src` stubs and the full English documentation should be available by the specified path. This feature performs the following validations: @@ -759,19 +756,18 @@ This feature performs the following validations: Running it with the stub examples that are used in this guide, the following warnings are shown: -.. code:: shell +```shell Warning: Missing class synopsis for Number Warning: Missing class synopsis for Elephant Warning: Missing class synopsis for Atmosphere Warning: Missing method synopsis for fahrenheitToCelsius() Warning: Missing method synopsis for Atmosphere::calculateBar() +``` -********************** - Parameter Statistics -********************** +## Parameter Statistics -The ``gen_stub.php`` flag ``--parameter-stats`` counts how many times a parameter name occurs in the +The `gen_stub.php` flag `--parameter-stats` counts how many times a parameter name occurs in the codebase. A JSON object is displayed, containing the parameter names and the number of their occurrences in diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst deleted file mode 100644 index 40e273b6fd71..000000000000 --- a/docs/source/miscellaneous/writing-tests.rst +++ /dev/null @@ -1,2874 +0,0 @@ -############### - Writing Tests -############### - -****************** - phpt Test Basics -****************** - -The first thing you need to know about tests is that we need more!!! Although PHP works just great -99.99% of the time, not having a very comprehensive test suite means that we take more risks every -time we add to or modify the PHP implementation. The second thing you need to know is that if you -can write PHP you can write tests. Thirdly — we are a friendly and welcoming community, don't be -scared about writing to (php-qa@lists.php.net) — we won't bite! - -So what are phpt tests? - - A phpt test is a little script used by the php internal and quality assurance teams to test PHP's - functionality. It can be used with new releases to make sure they can do all the things that - previous releases can, or to help find bugs in current releases. By writing phpt tests you are - helping to make PHP more stable. - -What skills are needed to write a phpt test? - - All that is really needed to write a phpt test is a basic understanding of the PHP language, a - text editor, and a way to get the results of your code. That is it. So if you have been writing - and running PHP scripts already — you have everything you need. - -What do you write phpt tests on? - - Basically you can write a phpt test on one of the various php functions available. You can write - a test on a basic language function (a string function or an array function) , or a function - provided by one of PHP's numerous extensions (a mysql function or a image function or a mcrypt - function). - - You can find out what functions already have phpt tests by looking in the `html version - `_ of the git repository (``ext/standard/tests/`` is a good place - to start looking — though not all the tests currently written are in there). - - If you want more guidance than that you can always ask the PHP Quality Assurance Team on their - mailing list (php-qa@lists.php.net) where they would like you to direct your attentions. - -How is a phpt test used? - - When a test is called by the ``run-tests.php`` script it takes various parts of the phpt file to - name and create a .php file. That .php file is then executed. The output of the .php file is then - compared to a different section of the phpt file. If the output of the script "matches" the - output provided in the phpt script — it passes. - -What should a phpt test do? - - Basically — it should try and break the PHP function. It should check not only the functions - normal parameters, but it should also check edge cases. Intentionally generating an error is - allowed and encouraged. - -******************** - Writing phpt Tests -******************** - -Naming Conventions -================== - -Phpt tests follow a very strict naming convention. This is done to easily identify what each phpt -test is for. Tests should be named according to the following list: - -Tests for bugs - bug.phpt (bug17123.phpt) - -Tests for a function's basic behaviour - _basic.phpt (dba_open_basic.phpt) - -Tests for a function's error behaviour - _error.phpt (dba_open_error.phpt) - -Tests for variations in a function's behaviour - _variation.phpt (dba_open_variation.phpt) - -General tests for extensions - .phpt (dba_003.phpt) - -The convention of using _basic, _error and _variation was introduced when we found that writing a -single test case for each function resulted in unacceptably large test cases. It's quite hard to -debug problems when the test case generates 100s of lines of output. - -The "basic" test case for a function should just address the single most simple thing that the -function is designed to do. For example, if writing a test for the sin() function a basic test would -just be to check that sin() returns the correct values for some known angles — eg 30, 90, 180. - -The "error" tests for a function are test cases which are designed to provoke errors, warnings or -notices. There can be more than one error case, if so the convention is to name the test cases -mytest_error1.phpt, mytest_error2.phpt and so on. - -The "variation" tests are any tests that don't fit into "basic" or "error" tests. For example one -might use a variation tests to test boundary conditions. - -How big is a test case? -======================= - -Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The reason -for this is that if we break something in PHP and it breaks your test case we need to be able to -find out quite quickly what we broke, going through 1000s of line of test case output is not easy. -Having said that it's sometimes just not practical to stay within the 10 line guideline, in this -case you can help a lot by commenting the output. You may find plenty of much longer tests in PHP - -the small tests message is something that we learnt over time, in fact we are slowly going through -and splitting tests up when we need to. - -Comments -======== - -Comments help. Not an essay — just a couple of lines on what the objective of the test is. It may -seem completely obvious to you as you write it, but it might not be to someone looking at it later -on. - -Basic Format -============ - -A test must contain the sections TEST, FILE and either EXPECT or EXPECTF at a minimum. The example -below illustrates a minimal test. - -*ext/standard/tests/strings/strtr.phpt* - -.. code:: php - - --TEST-- - strtr() function — basic test for strtr() - --FILE-- - "hi", "hi"=>"hello", "a"=>"A", "world"=>"planet"); - var_dump(strtr("# hi all, I said hello world! #", $trans)); - ?> - --EXPECT-- - string(32) "# hello All, I sAid hi planet! #" - -As you can see the file is divided into several sections. The TEST section holds a one line title of -the phpt test, this should be a simple description and shouldn't ever exceed one line, if you need -to write more explanation add comments in the body of the test case. The phpt files name is used -when generating a .php file. The FILE section is used as the body of the .php file, so don't forget -to open and close your php tags. The EXPECT section is the part used as a comparison to see if the -test passes. It is a good idea to generate output with var_dump() calls. - -PHPT structure details -====================== - -A phpt test can have many more parts than just the minimum. In fact some of the mandatory parts have -alternatives that may be used if the situation warrants it. The phpt sections are documented here. - -Analyzing failing tests -======================= - -While writing tests you will probably run into tests not passing while you think they should. The -'make test' command provides you with debug information. Several files will be added per test in the -same directory as the .phpt file itself. Considering your test file is named foo.phpt, these files -provide you with information that can help you find out what went wrong: - -foo.diff - - A diff file between the expected output (be it in EXPECT, EXPECTF or another option) and the - actual output. - -foo.exp - - The expected output. - -foo.log - - A log containing expected output, actual output and results. Most likely very similar to info in - the other files. - -foo.out - - The actual output of your .phpt test part. - -foo.php - - The php code that was executed for this test. - -foo.sh - - An executable file that executes the test for you as it was executed during failure. - -Testing your test cases -======================= - -Most people who write tests for PHP don't have access to a huge number of operating systems but the -tests are run on every system that runs PHP. It's good to test your test on as many platforms as you -can — Linux and Windows are the most important, it's increasingly important to make sure that tests -run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — don't -worry, if you have karma, commit the test but watch php-qa@lists.php.net for reports of failures on -other platforms. If you don't have karma to commit have a look at the next section. - -When you are testing your test case it's really important to make sure that you clean up any -temporary resources (eg files) that you used in the test. There is a special ``--CLEAN--`` section -to help you do this — see `here <#clean>`_. - -Tests run in parallel by default. Mutable resources such as files, directories, ports, database -objects, and IPC identifiers must therefore be unique to each test. Read-only fixtures may be -shared. If a resource cannot be isolated, declare the narrowest applicable conflict using -``--CONFLICTS--`` or a ``CONFLICTS`` file. - -Another good check is to look at what lines of code in the PHP source your test case covers. This is -easy to do, there are some instructions on the `PHP Wiki -`_. - -What should I do with my test case when I've written and tested it? -=================================================================== - -The next step is to get someone to review it. If it's short you can paste it into a note and send it -to php-qa@lists.php.net. If the test is a bit too long for that then put it somewhere were people -can download it (`pastebin `_ is sometimes used). Appending tests to notes as -files doesn't work well - so please don't do that. Your note to php-qa@lists.php.net should say what -level of PHP you have tested it on and what platform(s) you've run it on. Someone from the PHP QA -group will review your test and reply to you. They may ask for some changes or suggest better ways -to do things, or they may commit it to PHP. - -Writing Portable PHP Tests -========================== - -Writing portable tests can be hard if you don't have access to all the many platforms that PHP can -run on. Do your best. If in doubt, don't disable a test. It is better that the test runs in as many -environments as possible. - -If you know a new test won't run in a specific environment, try to write the complementary test for -that environment. - -Make sure sets of data are consistently ordered. SQL queries are not guaranteed to return results in -the same order unless an ORDER BY clause is used. Directory listings are another example that can -vary: use an appropriate PHP function to sort them before printing. Both of these examples have -affected PHP tests in the past. - -Make sure that any test touching parsing or display of dates uses a hard-defined timezone — -preferable 'UTC'. It is important that this is defined in the file section using: - -.. code:: php - - date_default_timezone_set('UTC'); - -and not in the INI section. This is because of the order in which settings are checked which is: - -.. code:: - - date_default_timezone_set() -> TZ environmental -> INI setting -> System Setting - -If a TZ environmental variable is found the INI setting will be ignored. - -Tests that run, or only have matching EXPECT output, on 32bit platforms can use a SKIPIF section -like: - -.. code:: php - - --SKIPIF-- - - -Tests for 64bit platforms can use: - -.. code:: php - - --SKIPIF-- - - -To run a test only on Windows: - -.. code:: php - - --SKIPIF-- - - -To run a test only on Linux: - -.. code:: php - - --SKIPIF-- - - -To skip a test on Mac OS X Darwin: - -.. code:: php - - --SKIPIF-- - - -********** - Examples -********** - -EXPECTF -======= - -``/ext/standard/tests/strings/str_shuffle.phpt`` is a good example for using ``EXPECTF`` instead of -``EXPECT``. From time to time the algorithm used for shuffle changed and sometimes the machine used -to execute the code has influence on the result of shuffle. But it always returns a three character -string detectable by ``%s`` (that matches any string until the end of the line). Other scan-able -forms are ``%a`` for any amount of chars (at least one), ``%i`` for integers, ``%d`` for numbers -only, ``%f`` for floating point values, ``%c`` for single characters, ``%x`` for hexadecimal values, -``%w`` for any number of whitespace characters and ``%e`` for ``DIRECTORY_SEPARATOR`` (``'\'`` or -``'/'``). - -See also `EXPECTF <#expectf>`_ details. - -*/ext/standard/tests/strings/str_shuffle.phpt* - -.. code:: php - - --TEST-- - Testing str_shuffle. - --FILE-- - - --EXPECTF-- - string(3) "%s" - string(3) "123" - -EXPECTREGEX -=========== - -``/ext/standard/tests/strings/strings001.phpt`` is a good example for using ``EXPECTREGEX`` instead -of ``EXPECT``. This test also shows that in ``EXPECTREGEX`` some characters need to be escaped since -otherwise they would be interpreted as a regular expression. - -*/ext/standard/tests/strings/strings001.phpt* - -.. code:: php - - --TEST-- - Test whether strstr() and strrchr() are binary safe. - --FILE-- - - --EXPECTREGEX-- - string\(18\) \"nica\x00turska panica\" - string\(19\) \" nica\x00turska panica\" - -EXTENSIONS -========== - -Some tests depend on PHP extensions that may be unavailable. These extensions should be listed in -the ``EXTENSIONS`` section. If an extension is missing, PHP will try to find it in a shared module -and skip the test if it's not there. - -*/ext/sodium/tests/crypto_scalarmult.phpt* - -.. code:: php - - --TEST-- - Check for libsodium scalarmult - --EXTENSIONS-- - sodium - --FILE-- - - --FILE-- - [snip] - -Test script and ``SKIPIF`` code should be directly written into ``\*.phpt``. However, it is -recommended to use include files when more test scripts depend on the same ``SKIPIF`` code or when -certain test files need the same values for some input. - -Note: no file used by any test should have one of the following extensions: ".php", ".log", ".mem", -".exp", ".out" or ".diff". When you use an include file for the ``SKIPIF`` section it should be -named "skipif.inc" and an include file used in the ``FILE`` section of many tests should be named -"test.inc". - -************* - Final Notes -************* - -Cleaning up after running a test -================================ - -Sometimes test cases create files or directories as part of the test case and it's important to -remove these after the test ends, the ``--CLEAN--`` section is provided to help with this. - -The PHP code in the ``--CLEAN--`` section is executed separately from the code in the ``--FILE--`` -section. For example, this code: - -.. code:: php - - --TEST-- - Will fail to clean up - --FILE-- - - --CLEAN-- - - --EXPECT-- - -will not remove the temporary file because the variable $temp_filename is not defined in the -``--CLEAN--`` section. - -Here is a better way to write the code: - -.. code:: php - - --TEST-- - This will remove temporary files - --FILE-- - - --CLEAN-- - - --EXPECT-- - -Note the use of the ``__DIR__`` construct which will ensure that the temporary file is created in -the same directory as the phpt test script. - -When creating temporary files it is a good idea to use an extension that indicates the use of the -file, eg .tmp. It's also a good idea to avoid using extensions that are already used for other -purposes, eg .inc, .php. Similarly, it is helpful to give the temporary file a name that is clearly -related to the test case. For example, mytest.phpt should create mytest.tmp (or mytestN.tmp, N=1, -2,3,...) then if by any chance the temporary file isnt't removed properly it will be obvious which -test case created it. - -When writing and debugging a test case with a ``--CLEAN--`` section it is helpful to remember that -the php code in the ``--CLEAN--`` section is executed separately from the code in the ``--FILE--`` -section. For example, in a test case called mytest.phpt, code from the ``--FILE--`` section is run -from a file called mytest.php and code from the ``--CLEAN--`` section is run from a file called -mytest.clean.php. If the test passes, both the .php and .clean.php files are removed by -``run-tests.php``. You can prevent the removal by using the --keep option of ``run-tests.php``, this -is a very useful option if you need to check that the ``--CLEAN--`` section code is working as you -intended. - -Finally — if you are using CVS it's helpful to add the extension that you use for test-related -temporary files to the .cvsignore file — this will help to prevent you from accidentally checking -temporary files into CVS. - -Redirecting tests -================= - -Using ``--REDIRECTTEST--`` it is possible to redirect from one test to a bunch of other tests. That -way multiple extensions can refer to the same set of test scripts probably using it with a different -configuration. - -The block is eval'd and supposed to return an array describing how to redirect. The resulting array -must contain the key 'TEST' that stores the redirect target as a string. This string usually is the -directory where the test scripts are located and should be relative. Optionally you can use the -'ENV' as an array configuring the environment to be set when executing the tests. This way you can -pass configuration to the executed tests. - -Redirect tests may especially contain ``--SKIPIF--``, ``--ENV--``, and ``--ARGS--`` sections but -they no not use any ``--EXPECT--`` section. - -The redirected tests themselves are just normal tests. - -Error reporting in tests -======================== - -All tests should run correctly with error_reporting(E_ALL) and display_errors=1. This is the default -when called from ``run-tests.php``. If you have a good reason for lowering the error reporting, use -``--INI--`` section and comment this in your testcode. - -If your test intentionally generates a PHP warning message use $php_errormsg variable, which you can -then output. This will result in a consistent error message output across all platforms and PHP -configurations, preventing your test from failing due inconsistencies in the error message content. -Alternatively you can use ``--EXPECTF--`` and check for the message by replacing the path of the -source of the message with ``%s`` and the line number with ``%d``. The end of a message in a test -file ``example.phpt`` then looks like ``in %sexample.php on line %d``. We explicitly dropped the -last path divider as that is a system dependent character ``/`` or ``\``. - -Last bit -======== - -Often you want to run test scripts without ``run-tests.php`` by executing them on command line like -any other php script. But sometimes it disturbs having a long ``--EXPECT--`` block, so that you -don't see the actual output as it scrolls away overwritten by the blocks following the actual file -block. The workaround is to use terminate the ``--FILE--`` section with the two lines ``===DONE===`` -and ````. When doing so ``run-tests.php`` does not execute the line containing the -exit call as that would suppress leak messages. Actually ``run-tests.php`` ignores any part after a -line consisting only of ``===DONE===``. - -Here is an example: - -.. code:: php - - --TEST-- - Test hypot() — dealing with mixed number/character input - --INI-- - precision=14 - --FILE-- - - ===DONE=== - - --EXPECTF-- - 23abc :-33 float(40.224370722238) - ===DONE=== - -If executed as PHP script the output will stop after the code on the ``--FILE--`` section has been -run. - -*********** - Reference -*********** - -PHPT Sections -============= - -``--TEST--`` ------------- - -**Description:** Title of test as a single line short description. - -**Required:** Yes - -**Format:** Plain text. We recommend a single line only. - -Example 1 (snippet): - -.. code:: text - - --TEST-- - Test filter_input() with GET and POST data. - -Example 1 (full): :ref:`sample001.phpt` - -``--DESCRIPTION--`` -------------------- - -**Description:** If your test requires more than a single line title to adequately describe it, you -can use this section for further explanation. Multiple lines are allowed and besides being used for -information, this section is completely ignored by the test binary. - -**Required:** No - -**Format:** Plain text, multiple lines. - -Example 1 (snippet): - -.. code:: text - - --DESCRIPTION-- - This test covers both valid and invalid usages of filter_input() with INPUT_GET and INPUT_POST data and several different filter sanitizers. - -Example 1 (full): :ref:`sample001.phpt` - -``--CREDITS--`` ---------------- - -**Description:** Used to credit contributors without CVS commit rights, who put their name and email -on the first line. If the test was part of a TestFest event, then # followed by the name of the -event and the date (YYYY-MM-DD) on the second line. - -**Required:** No. For newly created tests the section should no longer be used for simple authorship -claims or listing all contributors who edited the test; as it is already accurately tracked by Git. -It may be used if more specific attribution is useful, for example to credit the original reporter -of a bug or a contributor who is not credited via `Co-authored-by` tag. - -**Format:** Name Email [Event] - -Example 1 (snippet): - -.. code:: text - - --CREDITS-- - Felipe Pena - -Example 1 (full): :ref:`sample001.phpt` - -Example 2 (snippet): - -.. code:: text - - --CREDITS-- - Zoe Slattery zoe@php.net - # TestFest Munich 2009-05-19 - -Example 2 (full): :ref:`sample002.phpt` - -``--SKIPIF--`` --------------- - -**Description:** A condition or set of conditions used to determine if a test should be skipped. -Tests that are only applicable to a certain platform, extension or PHP version are good reasons for -using a ``--SKIPIF--`` section. - -A common practice for extension tests is to write your ``--SKIPIF--`` extension criteria into a file -call skipif.inc and then including that file in the ``--SKIPIF--`` section of all your extension -tests. This promotes the DRY principle and reduces future code maintenance. - -**Required:** No. - -**Format:** PHP code enclosed by PHP tags. If the output of this scripts starts with "skip", the -test is skipped. If the output starts with "xfail", the test is marked as expected failure. If the -output starts with "flaky", the test is marked as flaky test. The "xfail" convention is supported as -of PHP 7.2.0. The "flaky" convention is supported as of PHP 8.2.25 and PHP 8.3.13, respectively. - -Example 1 (snippet): - -.. code:: php - - --SKIPIF-- - - -Example 1 (full): :ref:`sample001.phpt` - -Example 2 (snippet): - -.. code:: php - - --SKIPIF-- - - -Example 2 (full): :ref:`sample003.phpt` - -Example 3 (snippet): - -.. code:: php - - --SKIPIF-- - - -Example 3 (full): :ref:`xfailif.phpt` - -Example 4 (snippet): - -.. code:: php - - --SKIPIF-- - string

&d=12345.7 - -Example 1 (full): :ref:`sample001.phpt` - -Example 2 (snippet): - -.. code:: xml - - --POST-- - - - - - - -Example 2 (full): :ref:`sample005.phpt` - -``--POST_RAW--`` ----------------- - -**Description:** Raw POST data to be passed to the test script. This differs from the section above -because it doesn't automatically set the Content-Type, this leaves you free to define your own -within the section. This section forces the use of the CGI binary instead of the usual CLI one. - -**Required:** No. - -Requirements: PHP CGI binary. - -**Test Script Support:** ``run-tests.php`` - -**Format:** Follows the HTTP post data format. - -Example 1 (snippet): - -.. code:: text - - --POST_RAW-- - Content-type: multipart/form-data, boundary=AaB03x - - --AaB03x content-disposition: form-data; name="field1" - - Joe Blow - --AaB03x - content-disposition: form-data; name="pics"; filename="file1.txt" - Content-Type: text/plain - - abcdef123456789 - --AaB03x-- - -Example 1 (full): :ref:`sample006.phpt` - -``--PUT--`` ------------ - -**Description:** Similar to the section above, PUT data to be passed to the test script. This -section forces the use of the CGI binary instead of the usual CLI one. - -**Required:** No. - -Requirements: PHP CGI binary. - -**Test Script Support:** ``run-tests.php`` - -**Format:** Raw data optionally preceded by a Content-Type header. - -Example 1 (snippet): - -.. code:: text - - --PUT-- - Content-Type: text/json - - {"name":"default output handler","type":0,"flags":112,"level":0,"chunk_size":0,"buffer_size":16384,"buffer_used":3} - -``--GZIP_POST--`` ------------------ - -**Description:** When this section exists, the POST data will be gzencode()'d. This section forces -the use of the CGI binary instead of the usual CLI one. - -**Required:** No. - -**Test Script Support:** ``run-tests.php`` - -**Format:** Just add the content to be gzencode()'d in the section. - -Example 1 (snippet): - -.. code:: xml - - --GZIP_POST-- - - - - - - -Example 1 (full): :ref:`sample005.phpt` - -``--DEFLATE_POST--`` --------------------- - -**Description:** When this section exists, the POST data will be gzcompress()'ed. This section -forces the use of the CGI binary instead of the usual CLI one. - -**Required:** No. - -Requirements: - -**Test Script Support:** ``run-tests.php`` - -**Format:** Just add the content to be gzcompress()'ed in the section. - -Example 1 (snippet): - -.. code:: xml - - --DEFLATE_POST-- - - - - - - - -Example 1 (full): :ref:`sample007.phpt` - -``--GET--`` ------------ - -**Description:** GET variables to be passed to the test script. This section forces the use of the -CGI binary instead of the usual CLI one. - -**Required:** No. - -Requirements: PHP CGI binary. - -**Format:** A single line of text passed as the GET data to the script. - -Example 1 (snippet): - -.. code:: text - - --GET-- - a=test&b=http://example.com - -Example 1 (full): :ref:`sample001.phpt` - -Example 2 (snippet): - -.. code:: text - - --GET-- - ar[elm1]=1234&ar[elm2]=0660&a=0234 - -Example 2 (full): :ref:`sample008.phpt` - -``--COOKIE--`` --------------- - -**Description:** Cookies to be passed to the test script. This section forces the use of the CGI -binary instead of the usual CLI one. - -**Required:** No. - -Requirements: PHP CGI binary. - -**Test Script Support:** ``run-tests.php`` - -**Format:** A single line of text in a valid HTTP cookie format. - -Example 1 (snippet): - -.. code:: - - --COOKIE-- - hello=World;goodbye=MrChips - -Example 1 (full): :ref:`sample002.phpt` - -``--STDIN--`` -------------- - -**Description:** Data to be fed to the test script's standard input. - -**Required:** No. - -**Test Script Support:** ``run-tests.php`` - -**Format:** Any text within this section is passed as STDIN to PHP. - -Example 1 (snippet): - -.. code:: text - - --STDIN-- - fooBar - use this to input some thing to the php script - -Example 1 (full): :ref:`sample009.phpt` - -``--INI--`` ------------ - -**Description:** To be used if you need a specific php.ini setting for the test. - -**Required:** No. - -**Format:** Key value pairs including automatically replaced tags. One setting per line. Content -that is not a valid ini setting may cause failures. - -The following is a list of all tags and what they are used to represent: - -- ``{PWD}``: Represents the directory of the file containing the ``--INI--`` section. -- ``{TMP}``: Represents the system's temporary directory. Available as of PHP 7.2.19 and 7.3.6. - -Example 1 (snippet): - -.. code:: text - - --INI-- - precision=14 - -Example 1 (full): :ref:`sample001.phpt` - -Example 2 (snippet): - -.. code:: text - - --INI-- - session.use_cookies=0 - session.cache_limiter= - register_globals=1 - session.serialize_handler=php - session.save_handler=files - -Example 2 (full): :ref:`sample003.phpt` - -``--ARGS--`` ------------- - -**Description:** A single line defining the arguments passed to PHP. - -**Required:** No. - -**Format:** A single line of text that is passed as the argument(s) to the PHP CLI. - -Example 1 (snippet): - -.. code:: text - - --ARGS-- - --arg value --arg=value -avalue -a=value -a value - -Example 1 (full): :ref:`sample010.phpt` - -``--ENV--`` ------------ - -**Description:** Configures environment variables such as those found in the ``$_SERVER`` global -array. - -**Required:** No. - -**Format:** Key value pairs. One setting per line. - -Example 1 (snippet): - -.. code:: text - - --ENV-- - SCRIPT_NAME=/frontcontroller10.php - REQUEST_URI=/frontcontroller10.php/hi - PATH_INFO=/hi - -Example 1 (full): :ref:`sample018.phpt` - -``--PHPDBG--`` --------------- - -**Description:** This section takes arbitrary phpdbg commands and executes the test file according -to them as it would be run in the phpdbg prompt. - -**Required:** No. - -**Format:** Arbitrary phpdbg commands - -Example 1 (snippet): - -.. code:: text - - --PHPDBG-- - b - 4 - b - del - 0 - b - 5 - r - b - del - 1 - r - y - q - -Example 1 (full): :ref:`phpdbg_1.phpt` - -``--FILE--`` ------------- - -**Description:** The test source code. - -**Required:** One of the ``FILE`` type sections is required. - -**Format:** PHP source code enclosed by PHP tags. - -Example 1 (snippet): - -.. code:: php - - --FILE-- - - -Example 1 (full): :ref:`sample001.phpt` - -``--FILEEOF--`` ---------------- - -**Description:** An alternative to ``--FILE--`` where any trailing line breaks (\n || \r || \r\n -found at the end of the section) are omitted. This is an extreme edge-case feature, so 99.99% of the -time you won't need this section. - -**Required:** One of the ``FILE`` type sections is required. - -**Test Script Support:** ``run-tests.php`` - -**Format:** PHP source code enclosed by PHP tags. - -Example 1 (snippet): - -.. code:: php - - --FILEEOF-- - array( - 'PDOTEST_DSN' => 'sqlite2::memory:' - ), - 'TESTS' => 'ext/pdo/tests' - ); - -Example 1 (full): :ref:`sample013.phpt` Note: The destination tests for this example are not -included. See the PDO extension tests for reference to live tests using this section. - -Example 2 (snippet): - -.. code:: php - - --REDIRECTTEST-- - # magic auto-configuration - - $config = array( - 'TESTS' => 'ext/pdo/tests' - ); - - if (false !== getenv('PDO_MYSQL_TEST_DSN')) { - # user set them from their shell - $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN'); - $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER'); - $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS'); - if (false !== getenv('PDO_MYSQL_TEST_ATTR')) { - $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR'); - } - } else { - $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test'; - $config['ENV']['PDOTEST_USER'] = 'root'; - $config['ENV']['PDOTEST_PASS'] = ''; - } - - return $config; - -Example 2 (full): :ref:`sample014.phpt` - -Note: The destination tests for this example are not included. See the PDO extension tests for -reference to live tests using this section. - -``--CGI--`` ------------ - -**Description:** This section takes no value. It merely provides a simple marker for tests that MUST -be run as CGI, even if there is no ``--POST--`` or ``--GET--`` sections in the test file. - -**Required:** No. - -**Format:** No value, just the ``--CGI--`` statement. - -Example 1 (snippet): - -.. code:: text - - --CGI-- - -Example 1 (full): :ref:`sample016.phpt` - -``--XFAIL--`` -------------- - -**Description:** This section identifies this test as one that is currently expected to fail. It -should include a brief description of why it's expected to fail. Reasons for such expectations -include tests that are written before the functionality they are testing is implemented or notice of -a bug which is due to upstream code such as an extension which provides PHP support for some other -software. - -Please do NOT include an ``--XFAIL--`` without providing a text description for the reason it's -being used. - -**Required:** No. - -**Test Script Support:** ``run-tests.php`` - -**Format:** A short plain text description of why this test is currently expected to fail. - -Example 1 (snippet): - -.. code:: text - - --XFAIL-- - This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64 - -Example 1 (full): :ref:`sample017.phpt` - -``--FLAKY--`` -------------- - -**Description:** This section identifies this test as one that occasionally fails. If the test -actually fails, it will be retried one more time, and that result will be reported. The section -should include a brief description of why the test is flaky. Reasons for this include tests that -rely on relatively precise timing, or temporary disc states. Available as of PHP 8.1.22 and 8.2.9, -respectively. - -Please do NOT include a ``--FLAKY--`` section without providing a text description for the reason it -is being used. - -**Required:** No. - -**Test Script Support:** ``run-tests.php`` - -**Format:** A short plain text description of why this test is flaky. - -Example 1 (snippet): - -.. code:: - - --FLAKY-- - This test frequently fails in CI - -Example 1 (full): flaky.phpt - -``--EXPECTHEADERS--`` ---------------------- - -**Description:** The expected headers. Any header specified here must exist in the response and have -the same value or the test fails. Additional headers found in the actual tests while running are -ignored. - -**Required:** No. - -**Format:** HTTP style headers. May include multiple lines. - -Example 1 (snippet): - ---EXPECTHEADERS-- Status: 404 - -Example 1 (snippet): - -.. code:: text - - --EXPECTHEADERS-- - Content-type: text/html; charset=UTF-8 - Status: 403 Access Denied - -Example 1 (full): :ref:`sample018.phpt` - -Note: The destination tests for this example are not included. See the phar extension tests for -reference to live tests using this section. - -``--EXPECT--`` --------------- - -**Description:** The expected output from the test script. This must match the actual output from -the test script exactly for the test to pass. - -**Required:** One of the ``EXPECT`` type sections is required. - -**Format:** Plain text. Multiple lines of text are allowed. - -Example 1 (snippet): - -.. code:: text - - --EXPECT-- - array(2) { - ["hello"]=> - string(5) "World" - ["goodbye"]=> - string(7) "MrChips" - } - -Example 1 (full): :ref:`sample002.phpt` - -``--EXPECT_EXTERNAL--`` ------------------------ - -**Description:** Similar to ``--EXPECT--`` section, but just stating a filename where to load the -expected output from. - -**Required:** One of the ``EXPECT`` type sections is required. - -**Test Script Support:** ``run-tests.php`` - -Example 1 (snippet): - -.. code:: text - - --EXPECT_EXTERNAL-- - test001.expected.txt - -*test001.expected.txt* - -.. code:: php - - array(2) { - ["hello"]=> - string(5) "World" - ["goodbye"]=> - string(7) "MrChips" - } - -``--EXPECTF--`` ---------------- - -**Description:** An alternative of ``--EXPECT--``. Where it differs from ``--EXPECT--`` is that it -uses a number of substitution tags for strings, spaces, digits, etc. that appear in test case output -but which may vary between test runs. The most common example of this is to use %s and %d to match -the file path and line number which are output by PHP Warnings. - -**Required:** One of the ``EXPECT`` type sections is required. - -**Format:** Plain text including tags which are inserted to represent different types of output -which are not guaranteed to have the same value on subsequent runs or when run on different -platforms. - -The following is a list of all tags and what they are used to represent: - - - ``%e``: Represents a directory separator, for example / on Linux. - - ``%s``: One or more of anything (character or white space) except the end of line character. - - ``%S``: Zero or more of anything (character or white space) except the end of line character. - - ``%a``: One or more of anything (character or white space) including the end of line - character. - - ``%A``: Zero or more of anything (character or white space) including the end of line - character. - - ``%w``: Zero or more white space characters. - - ``%i``: A signed integer value, for example +3142, -3142, 3142. - - ``%d``: An unsigned integer value, for example 123456. - - ``%x``: One or more hexadecimal character. That is, characters in the range 0-9, a-f, A-F. - - ``%f``: A floating point number, for example: 3.142, -3.142, 3.142E-10, 3.142e+10. - - ``%c``: A single character of any sort (.). - - ``%r...%r``: Any string (...) enclosed between two ``%r`` will be treated as a regular - expression. - -Example 1 (snippet): - -.. code:: text - - --EXPECTF-- - string(4) "test" - string(18) "http://example.com" - string(27) "<b>test</b>" - - Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d - bool(false) - string(6) "string" - float(12345.7) - string(29) "<p>string</p>" - bool(false) - - Warning: filter_var() expects parameter 2 to be long, string given in %s011.php on line %d - NULL - - Warning: filter_input() expects parameter 3 to be long, string given in %s011.php on line %d - NULL - - Warning: filter_var() expects at most 3 parameters, 5 given in %s011.php on line %d - NULL - - Warning: filter_var() expects at most 3 parameters, 5 given in %s011.php on line %d - NULL - Done - -Example 1 (full): :ref:`sample001.phpt` - -Example 2 (snippet): - -.. code:: text - - --EXPECTF-- - Warning: bzopen() expects exactly 2 parameters, 0 given in %s on line %d NULL - - Warning: bzopen(): '' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d - bool(false) - - Warning: bzopen(): filename cannot be empty in %s on line %d - bool(false) - - Warning: bzopen(): filename cannot be empty in %s on line %d - bool(false) - - Warning: bzopen(): 'x' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d - bool(false) - - Warning: bzopen(): 'rw' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d - bool(false) - - Warning: bzopen(no_such_file): failed to open stream: No such file or directory in %s on line %d - bool(false) - resource(%d) of type (stream) Done - -Example 2 (full): :ref:`sample019.phpt` - -Example 3 (snippet): - -.. code:: text - - --EXPECTF-- - object(DOMNodeList)#%d (0) { - } - int(0) - bool(true) - bool(true) - string(0) "" - bool(true) - bool(true) - bool(false) - bool(false) - -Example 2 (full): :ref:`sample020.phpt` - -``--EXPECTF_EXTERNAL--`` ------------------------- - -**Description:** Similar to ``--EXPECTF--`` section, but like the ``--EXPECT_EXTERNAL--`` section -just stating a filename where to load the expected output from. - -**Required:** One of the ``EXPECT`` type sections is required. - -**Test Script Support:** ``run-tests.php`` - -``--EXPECTREGEX--`` -------------------- - -**Description:** An alternative of ``--EXPECT--``. This form allows the tester to specify the result -in a regular expression. - -**Required:** One of the ``EXPECT`` type sections is required. - -**Format:** Plain text including regular expression patterns which represent data that can vary -between subsequent runs of a test or when run on different platforms. - -Example 1 (snippet): - -.. code:: text - - --EXPECTREGEX-- - M_E : 2.718281[0-9]* - M_LOG2E : 1.442695[0-9]* - M_LOG10E : 0.434294[0-9]* - M_LN2 : 0.693147[0-9]* - M_LN10 : 2.302585[0-9]* - M_PI : 3.141592[0-9]* - M_PI_2 : 1.570796[0-9]* - M_PI_4 : 0.785398[0-9]* - M_1_PI : 0.318309[0-9]* - M_2_PI : 0.636619[0-9]* - M_SQRTPI : 1.772453[0-9]* - M_2_SQRTPI: 1.128379[0-9]* - M_LNPI : 1.144729[0-9]* - M_EULER : 0.577215[0-9]* - M_SQRT2 : 1.414213[0-9]* - M_SQRT1_2 : 0.707106[0-9]* - M_SQRT3 : 1.732050[0-9]* - -Example 1 (full): :ref:`sample021.phpt` - -Example 2 (snippet): - -.. code:: text - - --EXPECTF-- - *** Testing imap_append() : basic functionality *** - Create a new mailbox for test - Create a temporary mailbox and add 0 msgs - .. mailbox '%s' created - Add a couple of msgs to new mailbox {%s}INBOX.%s - bool(true) - bool(true) - Msg Count after append : 2 - List the msg headers - array(2) { - [0]=> - string(%d) "%w%s 1)%s webmaster@something. Test message (%d chars)" - [1]=> - string(%d) "%w%s 2)%s webmaster@something. Another test (%d chars)" - } - -Example 2 (full): :ref:`sample025.phpt` - -Example 3 (snippet): - -.. code:: text - - --EXPECTREGEX-- - string\(4\) \"-012\" - string\(8\) \"2d303132\" - (string\(13\) \" 4294967284\"|string\(20\) \"18446744073709551604\") - (string\(26\) \"20202034323934393637323834\"|string\(40\) \"3138343436373434303733373039353531363034\") - - Example 3 (full): :ref:`sample023.phpt` - -``--EXPECTREGEX_EXTERNAL--`` ----------------------------- - -**Description:** Similar to ``--EXPECTREGEX--`` section, but like the ``--EXPECT_EXTERNAL--`` -section just stating a filename where to load the expected output from. - -**Required:** One of the ``EXPECT`` type sections is required. - -**Test Script Support:** ``run-tests.php`` - -``--CLEAN--`` -------------- - -**Description:** Code that is executed after a test completes. It's main purpose is to allow you to -clean up after yourself. You might need to remove files created during the test or close sockets or -database connections following a test. Infact, even if a test fails or encounters a fatal error -during the test, the code found in the ``--CLEAN--`` section will still run. - -Code in the clean section is run in a completely different process than the one the test was run in. -So do not try accessing variables you created in the ``--FILE--`` section from inside the -``--CLEAN--`` section, they won't exist. - -Using the switch ``--no-clean`` on ``run-tests.php``, you can prevent the code found in the -``--CLEAN--`` section of a test from running. This allows you to inspect generated data or files -without them being removed by the ``--CLEAN--`` section. - -**Required:** No. - -**Test Script Support:** ``run-tests.php`` - -**Format:** PHP source code enclosed by PHP tags. - -Example 1 (snippet): - -.. code:: php - - --CLEAN-- - - -Example 1 (full): :ref:`sample024.phpt` - -Example 2 (snippet): - -.. code:: php - - --CLEAN-- - - -Example 2 (full): :ref:`sample025.phpt` - -Example 3 (snippet): - -.. code:: php - - --CLEAN-- - - -Example 3 (full): :ref:`sample022.phpt` - -Samples -======= - -capture_stdio_1.phpt --------------------- - -.. code:: php - - --TEST-- - Test covering the I/O stdin and stdout streams. - --DESCRIPTION-- - This tests checks if the output of stdin and stdout I/O streams match the - expected content. - --CAPTURE_STDIO-- - STDIN STDERR - --FILE-- - - --EXPECT-- - This is error sent to the stderr I/O stream - -capture_stdio_2.phpt --------------------- - -.. code:: php - - --TEST-- - Test covering the I/O stdin and stderr streams. - --DESCRIPTION-- - This tests checks if the output of stdin and stderr I/O streams match the - expected content. - --CAPTURE_STDIO-- - STDIN STDOUT - --FILE-- - - --EXPECT-- - Hello, world. This is sent to the stdout I/O stream - -capture_stdio_3.phpt --------------------- - -.. code:: php - - --TEST-- - Test covering the all standard I/O streams. - --DESCRIPTION-- - This tests checks if the output of stdin, stdout and stderr I/O streams match - the expected content. - --CAPTURE_STDIO-- - STDIN STDOUT STDERR - --FILE-- - - --EXPECT-- - Hello, world. This is sent to the stdout I/O stream - This is error sent to the stderr I/O stream - -clean.php ---------- - -.. code:: php - - Nmsgs; $i++) { - imap_delete($imap_stream, $i); - } - - $mailboxes = imap_getmailboxes($imap_stream, $server, '*'); - - foreach($mailboxes as $value) { - // Only delete mailboxes with our prefix - if (preg_match('/\{.*?\}INBOX\.(.+)/', $value->name, $match) == 1) { - if (strlen($match[1]) >= strlen($mailbox_prefix) - && substr_compare($match[1], $mailbox_prefix, 0, strlen($mailbox_prefix)) == 0) { - imap_deletemailbox($imap_stream, $value->name); - } - } - } - - imap_close($imap_stream, CL_EXPUNGE); - ?> - -conflicts_1.phpt ----------------- - -.. code:: php - - --TEST-- - Test get_headers() function : test with context - --CONFLICTS-- - server - --FILE-- - array( - 'method' => 'HEAD' - ) - ); - - $context = stream_context_create($opts); - $headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1, $context); - echo $headers["X-Request-Method"]."\n"; - - stream_context_set_default($opts); - $headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1); - echo $headers["X-Request-Method"]."\n"; - - echo "Done"; - ?> - --EXPECT-- - HEAD - HEAD - Done - -extensions.phpt ---------------- - -.. code:: php - - --TEST-- - phpt EXTENSIONS directive with shared extensions - --DESCRIPTION-- - This test covers the presence of some loaded extensions with a list of additional - extensions to be loaded when running test. - --EXTENSIONS-- - curl - imagick - tokenizer - --FILE-- - - --EXPECT-- - bool(true) - bool(true) - bool(true) - -file012.phpt ------------- - -.. code:: php - - - -phpdbg_1.phpt -------------- - -.. code:: php - - --TEST-- - Test deleting breakpoints - --PHPDBG-- - b 4 - b del 0 - b 5 - r - b del 1 - r - y - q - --EXPECTF-- - [Successful compilation of %s] - prompt> [Breakpoint #0 added at %s:4] - prompt> [Deleted breakpoint #0] - prompt> [Breakpoint #1 added at %s:5] - prompt> 12 - [Breakpoint #1 at %s:5, hits: 1] - >00005: echo $i++; - 00006: echo $i++; - 00007: - prompt> [Deleted breakpoint #1] - prompt> Do you really want to restart execution? (type y or n): 1234 - [Script ended normally] - prompt> - --FILE-- - - --INI-- - precision=14 - --SKIPIF-- - - --GET-- - a=test&b=https://example.com - --POST-- - c=

string

&d=12345.7 - --FILE-- - - --EXPECTF-- - string(4) "test" - string(19) "https://example.com" - string(27) "<b>test</b>" - - Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d - bool(false) - string(6) "string" - float(12345.7) - string(29) "<p>string</p>" - bool(false) - - Warning: filter_var() expects parameter 2 to be long, string given in %ssample001.php on line %d - NULL - - Warning: filter_input() expects parameter 3 to be long, string given in %ssample001.php on line %d - NULL - - Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d - NULL - - Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d - NULL - Done - -sample002.phpt --------------- - -.. code:: php - - --TEST-- - Test receipt of cookie data. - --CREDITS-- - Zoe Slattery zoe@php.net - # TestFest Munich 2009-05-19 - --COOKIE-- - hello=World;goodbye=MrChips - --FILE-- - - --EXPECT-- - array(2) { - ["hello"]=> - string(5) "World" - ["goodbye"]=> - string(7) "MrChips" - } - -sample003.phpt --------------- - -.. code:: php - - --TEST-- - session object deserialization - --SKIPIF-- - - --INI-- - session.use_cookies=0 - session.cache_limiter= - register_globals=1 - session.serialize_handler=php - session.save_handler=files - --FILE-- - yes++; } - } - - session_id("abtest"); - session_start(); - session_decode('baz|O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}arr|a:1:{i:3;O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}}'); - - $baz->method(); - $arr[3]->method(); - - var_dump($baz); - var_dump($arr); - session_destroy(); - --EXPECT-- - object(foo)#1 (2) { - ["bar"]=> - string(2) "ok" - ["yes"]=> - int(2) - } - array(1) { - [3]=> - object(foo)#2 (2) { - ["bar"]=> - string(2) "ok" - ["yes"]=> - int(2) - } - } - -sample005.phpt --------------- - -.. code:: php - - --TEST-- - SOAP Server 19: compressed request (gzip) - --SKIPIF-- - - --INI-- - precision=14 - --GZIP_POST-- - - - - - - --FILE-- - "http://testuri.org")); - $server->addfunction("test"); - $server->handle(); - echo "ok\n"; - ?> - --EXPECT-- - - Hello World - ok - -sample006.phpt --------------- - -.. code:: php - - --TEST-- - is_uploaded_file() function - --CREDITS-- - Dave Kelsey - --SKIPIF-- - - --POST_RAW-- - Content-type: multipart/form-data, boundary=AaB03x - - --AaB03x - content-disposition: form-data; name="field1" - - Joe Blow - --AaB03x - content-disposition: form-data; name="pics"; filename="file1.txt" - Content-Type: text/plain - - abcdef123456789 - --AaB03x-- - --FILE-- - - --EXPECTF-- - bool(true) - bool(false) - bool(false) - bool(false) - - Warning: is_uploaded_file() expects exactly 1 parameter, 0 given in %s on line %d - NULL - - Warning: is_uploaded_file() expects exactly 1 parameter, 2 given in %s on line %d - NULL - -sample007.phpt --------------- - -.. code:: php - - --TEST-- - SOAP Server 20: compressed request (deflate) - --SKIPIF-- - - --INI-- - precision=14 - --DEFLATE_POST-- - - - - - - - --FILE-- - "http://testuri.org")); - $server->addfunction("test"); - $server->handle(); - echo "ok\n"; - ?> - --EXPECT-- - - Hello World - ok - -sample008.phpt --------------- - -.. code:: php - - --TEST-- - GET/POST/REQUEST Test with input_filter - --SKIPIF-- - - --POST-- - d=379 - --GET-- - ar[elm1]=1234&ar[elm2]=0660&a=0234 - --FILE-- - FILTER_FLAG_ALLOW_OCTAL)); - var_dump($ret); - - $ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_REQUIRE_ARRAY)); - var_dump($ret); - - $ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_FLAG_ALLOW_OCTAL|FILTER_REQUIRE_ARRAY)); - var_dump($ret); - - ?> - --EXPECT-- - bool(false) - int(156) - array(2) { - ["elm1"]=> - int(1234) - ["elm2"]=> - bool(false) - } - array(2) { - ["elm1"]=> - int(1234) - ["elm2"]=> - int(432) - } - -sample009.phpt --------------- - -.. code:: php - - --TEST-- - STDIN input - --FILE-- - - --STDIN-- - fooBar - use this to input some thing to the php script - --EXPECT-- - string(54) "fooBar - use this to input some thing to the php script - " - -sample010.phpt --------------- - -.. code:: php - - --TEST-- - getopt#005 (Required values) - --ARGS-- - --arg value --arg=value -avalue -a=value -a value - --INI-- - register_argc_argv=On - variables_order=GPS - --FILE-- - - --EXPECT-- - array(2) { - ["arg"]=> - array(2) { - [0]=> - string(5) "value" - [1]=> - string(5) "value" - } - ["a"]=> - array(3) { - [0]=> - string(5) "value" - [1]=> - string(5) "value" - [2]=> - string(5) "value" - } - } - -sample011.phpt --------------- - -.. code:: php - - --TEST-- - Bug #35382 (Comment in end of file produces fatal error) - --FILEEOF-- - - --REDIRECTTEST-- - return array( - 'ENV' => array( - 'PDOTEST_DSN' => 'sqlite2::memory:' - ), - 'TESTS' => 'ext/pdo/tests' - ); - -sample014.phpt --------------- - -.. code:: php - - --TEST-- - MySQL - --SKIPIF-- - - --REDIRECTTEST-- - # magic auto-configuration - - $config = array( - 'TESTS' => 'ext/pdo/tests' - ); - - if (false !== getenv('PDO_MYSQL_TEST_DSN')) { - # user set them from their shell - $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN'); - $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER'); - $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS'); - if (false !== getenv('PDO_MYSQL_TEST_ATTR')) { - $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR'); - } - } else { - $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test'; - $config['ENV']['PDOTEST_USER'] = 'root'; - $config['ENV']['PDOTEST_PASS'] = ''; - } - - return $config; - -sample016.phpt --------------- - -.. code:: php - - --TEST-- - Test get variables with CGI binary - --GET-- - hello=World&goodbye=MrChips - --CGI-- - --FILE-- - - --EXPECT-- - array(2) { - ["hello"]=> - string(5) "World" - ["goodbye"]=> - string(7) "MrChips" - } - -sample017.phpt --------------- - -.. code:: php - - --TEST-- - PDO Common: Bug #34630 (inserting streams as LOBs) - --SKIPIF-- - - --FILE-- - getAttribute(PDO::ATTR_DRIVER_NAME); - $is_oci = $driver == 'oci'; - - if ($is_oci) { - $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val BLOB)'); - } else { - $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val VARCHAR(256))'); - } - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - - $fp = tmpfile(); - fwrite($fp, "I am the LOB data"); - rewind($fp); - - if ($is_oci) { - /* oracle is a bit different; you need to initiate a transaction otherwise - * the empty blob will be committed implicitly when the statement is - * executed */ - $db->beginTransaction(); - $insert = $db->prepare("insert into test (id, val) values (1, EMPTY_BLOB()) RETURNING val INTO :blob"); - } else { - $insert = $db->prepare("insert into test (id, val) values (1, :blob)"); - } - $insert->bindValue(':blob', $fp, PDO::PARAM_LOB); - $insert->execute(); - $insert = null; - - $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); - var_dump($db->query("SELECT * from test")->fetchAll(PDO::FETCH_ASSOC)); - - ?> - --XFAIL-- - This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64 - --EXPECT-- - array(1) { - [0]=> - array(2) { - ["id"]=> - string(1) "1" - ["val"]=> - string(17) "I am the LOB data" - } - } - -sample018.phpt --------------- - -.. code:: php - - --TEST-- - Phar front controller rewrite access denied [cache_list] - --INI-- - default_charset=UTF-8 - phar.cache_list={PWD}/frontcontroller10.php - --SKIPIF-- - - --ENV-- - SCRIPT_NAME=/frontcontroller10.php - REQUEST_URI=/frontcontroller10.php/hi - PATH_INFO=/hi - --FILE_EXTERNAL-- - files/frontcontroller4.phar - --EXPECTHEADERS-- - Content-type: text/html; charset=UTF-8 - Status: 403 Access Denied - --EXPECT-- - - - Access Denied - - -

403 - File /hi Access Denied

- - - -sample019.phpt --------------- - -.. code:: php - - --TEST-- - bzopen() and invalid parameters - --SKIPIF-- - - --FILE-- - - --EXPECTF-- - Warning: bzopen() expects exactly 2 parameters, 0 given in %s on line %d - NULL - - Warning: bzopen(): '' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d - bool(false) - - Warning: bzopen(): filename cannot be empty in %s on line %d - bool(false) - - Warning: bzopen(): filename cannot be empty in %s on line %d - bool(false) - - Warning: bzopen(): 'x' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d - bool(false) - - Warning: bzopen(): 'rw' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d - bool(false) - - Warning: bzopen(no_such_file): failed to open stream: No such file or directory in %s on line %d - bool(false) - resource(%d) of type (stream) - Done - -sample020.phpt --------------- - -.. code:: php - - --TEST-- - Bug #42082 (NodeList length zero should be empty) - --FILE-- - query('*'); - var_dump($nodes); - var_dump($nodes->length); - $length = $nodes->length; - var_dump(empty($nodes->length), empty($length)); - - $doc->loadXML(""); - var_dump($doc->firstChild->nodeValue, empty($doc->firstChild->nodeValue), isset($doc->firstChild->nodeValue)); - var_dump(empty($doc->nodeType), empty($doc->firstChild->nodeType)) - ?> - --EXPECTF-- - object(DOMNodeList)#%d (0) { - } - int(0) - bool(true) - bool(true) - string(0) "" - bool(true) - bool(true) - bool(false) - bool(false) - -sample021.phpt --------------- - -.. code:: php - - --TEST-- - Math constants - --INI-- - precision=14 - --FILE-- - - --EXPECTREGEX-- - M_E : 2.718281[0-9]* - M_LOG2E : 1.442695[0-9]* - M_LOG10E : 0.434294[0-9]* - M_LN2 : 0.693147[0-9]* - M_LN10 : 2.302585[0-9]* - M_PI : 3.141592[0-9]* - M_PI_2 : 1.570796[0-9]* - M_PI_4 : 0.785398[0-9]* - M_1_PI : 0.318309[0-9]* - M_2_PI : 0.636619[0-9]* - M_SQRTPI : 1.772453[0-9]* - M_2_SQRTPI: 1.128379[0-9]* - M_LNPI : 1.144729[0-9]* - M_EULER : 0.577215[0-9]* - M_SQRT2 : 1.414213[0-9]* - M_SQRT1_2 : 0.707106[0-9]* - M_SQRT3 : 1.732050[0-9]* - -sample022.phpt --------------- - -.. code:: php - - --TEST-- - shm_detach() tests - --SKIPIF-- - - --FILE-- - - --CLEAN-- - - --EXPECTF-- - Warning: shm_detach() expects exactly 1 parameter, 0 given in %ssample022.php on line %d - NULL - - Warning: shm_detach() expects exactly 1 parameter, 2 given in %ssample022.php on line %d - NULL - bool(true) - - Warning: shm_detach(): %d is not a valid sysvshm resource in %ssample022.php on line %d - bool(false) - - Warning: shm_remove(): %d is not a valid sysvshm resource in %ssample022.php on line %d - - Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d - NULL - - Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d - NULL - - Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d - NULL - Done - -sample023.phpt --------------- - -.. code:: php - - --TEST-- - Bug #23894 (sprintf() decimal specifiers problem) - --FILE-- - - --EXPECTREGEX-- - string\(4\) \"-012\" - string\(8\) \"2d303132\" - (string\(13\) \" 4294967284\"|string\(20\) \"18446744073709551604\") - (string\(26\) \"20202034323934393637323834\"|string\(40\) \"3138343436373434303733373039353531363034\") - -sample024.phpt --------------- - -.. code:: php - - --TEST-- - DOMDocument::save Test basic function of save method - --SKIPIF-- - - --FILE-- - formatOutput = true; - - $root = $doc->createElement('book'); - - $root = $doc->appendChild($root); - - $title = $doc->createElement('title'); - $title = $root->appendChild($title); - - $text = $doc->createTextNode('This is the title'); - $text = $title->appendChild($text); - - $temp_filename = __DIR__.'/DomDocument_save_basic.tmp'; - - echo 'Wrote: ' . $doc->save($temp_filename) . ' bytes'; // Wrote: 72 bytes - ?> - --CLEAN-- - - --EXPECTF-- - Wrote: 72 bytes - -sample025.phpt --------------- - -.. code:: php - - --TEST-- - Test imap_append() function : basic functionality - --SKIPIF-- - - --FILE-- - Mailbox . "\n"; - var_dump(imap_append($imap_stream, $mb_details->Mailbox - , "From: webmaster@something.com\r\n" - . "To: info@something.com\r\n" - . "Subject: Test message\r\n" - . "\r\n" - . "this is a test message, please ignore\r\n" - )); - - var_dump(imap_append($imap_stream, $mb_details->Mailbox - , "From: webmaster@something.com\r\n" - . "To: info@something.com\r\n" - . "Subject: Another test\r\n" - . "\r\n" - . "this is another test message, please ignore it too!!\r\n" - )); - - $check = imap_check($imap_stream); - echo "Msg Count after append : ". $check->Nmsgs . "\n"; - - echo "List the msg headers\n"; - var_dump(imap_headers($imap_stream)); - - imap_close($imap_stream); - ?> - --CLEAN-- - - --EXPECTF-- - *** Testing imap_append() : basic functionality *** - Create a new mailbox for test - Create a temporary mailbox and add 0 msgs - .. mailbox '%s' created - Add a couple of msgs to new mailbox {%s}INBOX.%s - bool(true) - bool(true) - Msg Count after append : 2 - List the msg headers - array(2) { - [0]=> - string(%d) "%w%s 1)%s webmaster@something. Test message (%d chars)" - [1]=> - string(%d) "%w%s 2)%s webmaster@something. Another test (%d chars)" - } - -sample026.phpt --------------- - -.. code:: php - - --TEST-- - SPL: ArrayIterator implementing RecursiveIterator - --FILE-- - array(21, 22 => array(221, 222), 23 => array(231)), 3); - - $dir = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::LEAVES_ONLY); - - foreach ($dir as $file) { - print "$file\n"; - } - - ?> - ===DONE=== - - --EXPECT-- - 1 - 21 - 221 - 222 - 231 - 3 - -skipif2.phpt ------------- - -.. code:: php - - - -skipif.phpt ------------ - -.. code:: php - - - -xfailif.phpt ------------- - -.. code:: php - - --TEST-- - Handling of errors during linking - --INI-- - opcache.enable=1 - opcache.enable_cli=1 - opcache.optimization_level=-1 - opcache.preload={PWD}/preload_inheritance_error_ind.inc - --SKIPIF-- - - --FILE-- - - --EXPECTF-- - Fatal error: Declaration of B::foo($bar) must be compatible with A::foo() in %spreload_inheritance_error.inc on line 8 diff --git a/docs/source/testing/running-tests/index.md b/docs/source/testing/running-tests/index.md new file mode 100644 index 000000000000..a248c74f8424 --- /dev/null +++ b/docs/source/testing/running-tests/index.md @@ -0,0 +1,179 @@ +# Running Tests + +The easiest way to test your PHP build is to run make test from the command line after successfully +compiling. This will run the all tests for all enabled functionalities and extensions located in +tests folders under the source root directory using the PHP CLI binary. + +`make test` executes the `run-tests.php` script under the source root (parallel builds will not +work). Therefore you can execute the script as follows: + +```shell + + sapi/cli/php [-c /path/to/php.ini] run-tests.php [ext/foo/tests/GLOB] +``` + +## Which php executable does make test use? + +If you are running the `run-tests.php` script from the command line (as above) you can set the +`TEST_PHP_EXECUTABLE` environment variable to explicitly select the PHP executable that is to be +tested, that is, used to run the test scripts, otherwise it will use the PHP CLI binary that you +have compiled (`sapi/cli/php`). + +If you run the tests using make test, the PHP CLI and CGI executables are automatically set for you. +`make test` executes `run-tests.php` script with the CLI binary. Some test scripts such as +session must be executed by CGI SAPI. Therefore, you must build PHP with CGI SAPI to perform all +tests. + +> [!NOTE] +> The PHP binary executing `run-tests.php` and the PHP binary used for executing test scripts may +> differ. If you use different PHP binary for executing `run-tests.php` script, you may get errors. + +## Which php.ini is used? + +`make test` uses the same `php.ini` file as it would once installed. The tests have been written +to be independent of that `php.ini` file, so if you find a test that is affected by a setting, +please report this, so we can address the issue. + +## Which test scripts are executed? + +The `run-tests.php` (`make test`), without any arguments executes all test scripts by extracting +all directories named tests from the source root and any subdirectories below. If there are files, +which have a phpt extension, `run-tests.php` looks at the sections in these files, determines +whether it should run it, by evaluating the `SKIPIF` section. If the test is eligible for +execution, the `FILE` section is extracted into a `.php` file (with the same name besides the +extension) and gets executed. When an argument is given or `TESTS` environment variable is set, +the GLOB is expanded by the shell and any file with extension `*.phpt` is regarded as a test file. + +Tester can easily execute tests selectively with as follows: + +```shell + + ./sapi/cli/php run-tests.php ext/mbstring/* + ./sapi/cli/php run-tests.php ext/mbstring/020.phpt +``` + +## Test Runner Options + +The `run-tests.php` test runner has many options. You can see these options by using the `-h` +option with `run-tests.php`. + +You can set options by specifying them on the command line when you run `php run-tests.php` or if +you use `make test` through the `TEST_PHP_ARGS` environment variable: + +```shell + + php run-tests.php -j24 + # or + TEST_PHP_ARGS="-j24" make test +``` + +### Running Tests in Parallel + +The test runner can run tests in parallel, by using the `-j` option: + +```shell + + php run-tests.php -j24 ext/date/*.phpt +``` + +## Test results + +Test results are printed to standard output. If there is a failed test, the `run-tests.php` script +saves the result, the expected result and the code executed to the test script directory. For +example, if `ext/myext/tests/myext.phpt` fails to pass, the following files are created: + +- `ext/myext/tests/myext.php` - actual test file executed +- `ext/myext/tests/myext.log` - log of test execution (L) +- `ext/myext/tests/myext.exp` - expected output (E) +- `ext/myext/tests/myext.out` - output from test script (O) +- `ext/myext/tests/myext.diff` - diff of .out and .exp (D) + +Failed tests are always bugs. Either the test is bugged or not considering factors applying to the +tester's environment, or there is a bug in PHP. If this is a known bug, we strive to provide bug +numbers, in either the test name or the file name. You can check the status of such a bug, by going +to: `https://bugs.php.net/12345` where 12345 is the bug number. For clarity and automated +processing, bug numbers are prefixed by a hash sign '#' in test names and/or test cases are named +`bug12345.phpt`. + +> [!NOTE] +> The files generated by tests can be selected by setting the environment variable +> `TEST_PHP_LOG_FORMAT`. For each file you want to be generated use the character in brackets as +> shown above (default is LEOD). The php file will be generated always. + +> [!NOTE] +> You can set environment variable `TEST_PHP_DETAILED` to enable detailed test information. + +## Automated testing + +If you like to keep up to speed, with latest developments and quality assurance, setting the +environment variable `NO_INTERACTION` to 1, will not prompt the tester for any user input. + +Normally, the exit status of make test is zero, regardless of the results of independent tests. Set +the environment variable `REPORT_EXIT_STATUS` to `1`, and make test will set the exit status +("\$?") to non-zero, when an individual test has failed. + +Example script to be run by cron: + +```shell + + ========== qa-test.sh ============= + #!/bin/sh + + CO_DIR=$HOME/cvs/php7 + MYMAIL=qa-test@domain.com + TMPDIR=/var/tmp + TODAY=`date +"%Y%m%d"` + + # Make sure compilation environment is correct + CONFIGURE_OPTS='--disable-all --enable-cli --with-pcre' + export MAKE=gmake + export CC=gcc + + # Set test environment + export NO_INTERACTION=1 + export REPORT_EXIT_STATUS=1 + + cd $CO_DIR + cvs update . >>$TMPDIR/phpqatest.$TODAY + ./cvsclean ; ./buildconf ; ./configure $CONFIGURE_OPTS ; $MAKE + $MAKE test >>$TMPDIR/phpqatest.$TODAY 2>&1 + if test $? -gt 0 + then + cat $TMPDIR/phpqatest.$TODAY | mail -s"PHP-QA Test Failed for $TODAY" $MYMAIL + fi + ========== end of qa-test.sh ============= +``` + +> [!NOTE] +> The exit status of `run-tests.php` will be `1` when `REPORT_EXIT_STATUS` is set. The result of +> make test may be higher than that. At present, gmake 3.79.1 returns 2, so it is advised to test +> for non-zero, rather then a specific value. + +When `make test` finished running tests, and if there are any failed tests, the script asks to +send the logs to the PHP QA mailing list. Please answer `y` to this question so that we can +efficiently process the results, entering your e-mail address (which will not be transmitted in +plain text to any list) enables us to ask you some more information if a test failed. Note that this +script also uploads php -i output so your hostname may be transmitted. + +Specific tests can also be executed, like running tests for a certain extension. To do this you can +do like so (for example the standard library): + +```shell + + make test TESTS=ext/standard. +``` + +Where `TESTS=` points to a directory containing .phpt files or a single .phpt file like: + +```shell + + make test TESTS=tests/basic/001.phpt. +``` + +You can also pass options directly to the underlying script that runs the test suite +(`run-tests.phpt`) using `TESTS=`, for example to check for memory leaks using Valgrind, the +`-m` option can be passed along: `make test TESTS="-m Zend/"`. For a full list of options that +can be passed along, then run `make test TESTS=-h`. + +*Windows users:* On Windows the `make` command is called `nmake` instead of `make`. This means +that on Windows you will have to run `nmake test`, to run the test suite. diff --git a/docs/source/testing/writing-tests/index.md b/docs/source/testing/writing-tests/index.md new file mode 100644 index 000000000000..b1017d16958c --- /dev/null +++ b/docs/source/testing/writing-tests/index.md @@ -0,0 +1,407 @@ +# Writing Tests + +```{toctree} + :hidden: + +Basics +sections/index +samples/index +``` + +The first thing you need to know about tests is that we need more!!! Although PHP works just great +99.99% of the time, not having a very comprehensive test suite means that we take more risks every +time we add to or modify the PHP implementation. The second thing you need to know is that if you +can write PHP you can write tests. Thirdly — we are a friendly and welcoming community, don't be +scared about writing to ([php-qa@lists.php.net](mailto:php-qa@lists.php.net)) — we won't bite! + +So what are phpt tests? + + A phpt test is a little script used by the php internal and quality assurance teams to test PHP's + functionality. It can be used with new releases to make sure they can do all the things that + previous releases can, or to help find bugs in current releases. By writing phpt tests you are + helping to make PHP more stable. + +What skills are needed to write a phpt test? + + All that is really needed to write a phpt test is a basic understanding of the PHP language, a + text editor, and a way to get the results of your code. That is it. So if you have been writing + and running PHP scripts already — you have everything you need. + +What do you write phpt tests on? + + Basically you can write a phpt test on one of the various php functions available. You can write + a test on a basic language function (a string function or an array function) , or a function + provided by one of PHP's numerous extensions (a mysql function or a image function or a mcrypt + function). + + You can find out what functions already have phpt tests by looking in the [html version](https://github.com/php/php-src) of the git repository (`ext/standard/tests/` is a good place + to start looking — though not all the tests currently written are in there). + + If you want more guidance than that you can always ask the PHP Quality Assurance Team on their + mailing list ([php-qa@lists.php.net](mailto:php-qa@lists.php.net)) where they would like you to direct your attentions. + +How is a phpt test used? + + When a test is called by the `run-tests.php` script it takes various parts of the phpt file to + name and create a .php file. That .php file is then executed. The output of the .php file is then + compared to a different section of the phpt file. If the output of the script "matches" the + output provided in the phpt script — it passes. + +What should a phpt test do? + + Basically — it should try and break the PHP function. It should check not only the functions + normal parameters, but it should also check edge cases. Intentionally generating an error is + allowed and encouraged. + +## Naming Conventions + +Phpt tests follow a very strict naming convention. This is done to easily identify what each phpt +test is for. Tests should be named according to the following list: + +- Tests for bugs + - `bug.phpt` (`bug17123.phpt`) +- Tests for a function's basic behaviour + - `_basic.phpt` (`dba_open_basic.phpt`) +- Tests for a function's error behaviour + - `_error.phpt` (`dba_open_error.phpt`) +- Tests for variations in a function's behaviour + - `_variation.phpt` (`dba_open_variation.phpt`) +- General tests for extensions + - `.phpt` (`dba_003.phpt`) + +The convention of using \_basic, \_error and \_variation was introduced when we found that writing a +single test case for each function resulted in unacceptably large test cases. It's quite hard to +debug problems when the test case generates 100s of lines of output. + +The "basic" test case for a function should just address the single most simple thing that the +function is designed to do. For example, if writing a test for the sin() function a basic test would +just be to check that sin() returns the correct values for some known angles — eg 30, 90, 180. + +The "error" tests for a function are test cases which are designed to provoke errors, warnings or +notices. There can be more than one error case, if so the convention is to name the test cases +mytest_error1.phpt, mytest_error2.phpt and so on. + +The "variation" tests are any tests that don't fit into "basic" or "error" tests. For example one +might use a variation tests to test boundary conditions. + +## How big is a test case? + +Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The reason +for this is that if we break something in PHP and it breaks your test case we need to be able to +find out quite quickly what we broke, going through 1000s of line of test case output is not easy. +Having said that it's sometimes just not practical to stay within the 10 line guideline, in this +case you can help a lot by commenting the output. You may find plenty of much longer tests in PHP - +the small tests message is something that we learnt over time, in fact we are slowly going through +and splitting tests up when we need to. + +## Comments + +Comments help. Not an essay — just a couple of lines on what the objective of the test is. It may +seem completely obvious to you as you write it, but it might not be to someone looking at it later +on. + +## Minimal Test Layout + +A test must contain the sections TEST, FILE and either EXPECT or EXPECTF at a minimum. The example +below illustrates a minimal test. + +*ext/standard/tests/strings/strtr.phpt* + +```php + + --TEST-- + strtr() function — basic test for strtr() + --FILE-- + "hi", "hi"=>"hello", "a"=>"A", "world"=>"planet"); + var_dump(strtr("# hi all, I said hello world! #", $trans)); + ?> + --EXPECT-- + string(32) "# hello All, I sAid hi planet! #" +``` + +As you can see the file is divided into several sections. The TEST section holds a one line title of +the phpt test, this should be a simple description and shouldn't ever exceed one line, if you need +to write more explanation add comments in the body of the test case. The phpt files name is used +when generating a .php file. The FILE section is used as the body of the .php file, so don't forget +to open and close your php tags. The EXPECT section is the part used as a comparison to see if the +test passes. It is a good idea to generate output with var_dump() calls. + +## Analyzing Failures + +While writing tests you will probably run into tests not passing while you think they should. The +'make test' command provides you with debug information. Several files will be added per test in the +same directory as the .phpt file itself. Considering your test file is named foo.phpt, these files +provide you with information that can help you find out what went wrong: + +foo.diff + + A diff file between the expected output (be it in EXPECT, EXPECTF or another option) and the + actual output. + +foo.exp + + The expected output. + +foo.log + + A log containing expected output, actual output and results. Most likely very similar to info in + the other files. + +foo.out + + The actual output of your .phpt test part. + +foo.php + + The php code that was executed for this test. + +foo.sh + + An executable file that executes the test for you as it was executed during failure. + +## Testing your test cases + +Most people who write tests for PHP don't have access to a huge number of operating systems but the +tests are run on every system that runs PHP. It's good to test your test on as many platforms as you +can — Linux and Windows are the most important, it's increasingly important to make sure that tests +run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — don't +worry, if you have karma, commit the test but watch [php-qa@lists.php.net](mailto:php-qa@lists.php.net) for reports of failures on +other platforms. If you don't have karma to commit have a look at the next section. + +When you are testing your test case it's really important to make sure that you clean up any +temporary resources (eg files) that you used in the test. There is a special `--CLEAN--` section +to help you do this — see [here](sections/index.md#--clean--). + +Tests run in parallel by default. Mutable resources such as files, directories, ports, database +objects, and IPC identifiers must therefore be unique to each test. Read-only fixtures may be +shared. If a resource cannot be isolated, declare the narrowest applicable conflict using +`--CONFLICTS--` or a `CONFLICTS` file. + +Another good check is to look at what lines of code in the PHP source your test case covers. This is +easy to do, there are some instructions on the [PHP Wiki](https://wiki.php.net/doc/articles/writing-tests). + +## Portability + +Writing portable tests can be hard if you don't have access to all the many platforms that PHP can +run on. Do your best. If in doubt, don't disable a test. It is better that the test runs in as many +environments as possible. + +If you know a new test won't run in a specific environment, try to write the complementary test for +that environment. + +Make sure sets of data are consistently ordered. SQL queries are not guaranteed to return results in +the same order unless an ORDER BY clause is used. Directory listings are another example that can +vary: use an appropriate PHP function to sort them before printing. Both of these examples have +affected PHP tests in the past. + +Make sure that any test touching parsing or display of dates uses a hard-defined timezone — +preferable 'UTC'. It is important that this is defined in the file section using: + +```php + + date_default_timezone_set('UTC'); +``` + +and not in the INI section. This is because of the order in which settings are checked which is: + +``` + + date_default_timezone_set() -> TZ environmental -> INI setting -> System Setting +``` + +If a TZ environmental variable is found the INI setting will be ignored. + +Tests that run, or only have matching EXPECT output, on 32bit platforms can use a SKIPIF section +like: + +```php + + --SKIPIF-- + +``` + +Tests for 64bit platforms can use: + +```php + + --SKIPIF-- + +``` + +To run a test only on Windows: + +```php + + --SKIPIF-- + +``` + +To run a test only on Linux: + +```php + + --SKIPIF-- + +``` + +To skip a test on Mac OS X Darwin: + +```php + + --SKIPIF-- + +``` + +## Final Notes + +### Cleaning up after running a test + +Sometimes test cases create files or directories as part of the test case and it's important to +remove these after the test ends, the `--CLEAN--` section is provided to help with this. + +The PHP code in the `--CLEAN--` section is executed separately from the code in the `--FILE--` +section. For example, this code: + +```php + + --TEST-- + Will fail to clean up + --FILE-- + + --CLEAN-- + + --EXPECT-- +``` + +will not remove the temporary file because the variable \$temp_filename is not defined in the +`--CLEAN--` section. + +Here is a better way to write the code: + +```php + + --TEST-- + This will remove temporary files + --FILE-- + + --CLEAN-- + + --EXPECT-- +``` + +Note the use of the `__DIR__` construct which will ensure that the temporary file is created in +the same directory as the phpt test script. + +When creating temporary files it is a good idea to use an extension that indicates the use of the +file, eg .tmp. It's also a good idea to avoid using extensions that are already used for other +purposes, eg .inc, .php. Similarly, it is helpful to give the temporary file a name that is clearly +related to the test case. For example, mytest.phpt should create mytest.tmp (or mytestN.tmp, N=1, +2,3,...) then if by any chance the temporary file isnt't removed properly it will be obvious which +test case created it. + +When writing and debugging a test case with a `--CLEAN--` section it is helpful to remember that +the php code in the `--CLEAN--` section is executed separately from the code in the `--FILE--` +section. For example, in a test case called mytest.phpt, code from the `--FILE--` section is run +from a file called mytest.php and code from the `--CLEAN--` section is run from a file called +mytest.clean.php. If the test passes, both the .php and .clean.php files are removed by +`run-tests.php`. You can prevent the removal by using the --keep option of `run-tests.php`, this +is a very useful option if you need to check that the `--CLEAN--` section code is working as you +intended. + +Finally — if you are using CVS it's helpful to add the extension that you use for test-related +temporary files to the .cvsignore file — this will help to prevent you from accidentally checking +temporary files into CVS. + +### Redirecting tests + +Using `--REDIRECTTEST--` it is possible to redirect from one test to a bunch of other tests. That +way multiple extensions can refer to the same set of test scripts probably using it with a different +configuration. + +The block is eval'd and supposed to return an array describing how to redirect. The resulting array +must contain the key 'TEST' that stores the redirect target as a string. This string usually is the +directory where the test scripts are located and should be relative. Optionally you can use the +'ENV' as an array configuring the environment to be set when executing the tests. This way you can +pass configuration to the executed tests. + +Redirect tests may especially contain `--SKIPIF--`, `--ENV--`, and `--ARGS--` sections but +they no not use any `--EXPECT--` section. + +The redirected tests themselves are just normal tests. + +### Error reporting in tests + +All tests should run correctly with error_reporting(E_ALL) and display_errors=1. This is the default +when called from `run-tests.php`. If you have a good reason for lowering the error reporting, use +`--INI--` section and comment this in your testcode. + +If your test intentionally generates a PHP warning message use \$php_errormsg variable, which you can +then output. This will result in a consistent error message output across all platforms and PHP +configurations, preventing your test from failing due inconsistencies in the error message content. +Alternatively you can use `--EXPECTF--` and check for the message by replacing the path of the +source of the message with `%s` and the line number with `%d`. The end of a message in a test +file `example.phpt` then looks like `in %sexample.php on line %d`. We explicitly dropped the +last path divider as that is a system dependent character `/` or `\`. + +### Last bit + +Often you want to run test scripts without `run-tests.php` by executing them on command line like +any other php script. But sometimes it disturbs having a long `--EXPECT--` block, so that you +don't see the actual output as it scrolls away overwritten by the blocks following the actual file +block. The workaround is to use terminate the `--FILE--` section with the two lines `===DONE===` +and ``. When doing so `run-tests.php` does not execute the line containing the +exit call as that would suppress leak messages. Actually `run-tests.php` ignores any part after a +line consisting only of `===DONE===`. + +Here is an example: + +```php + + --TEST-- + Test hypot() — dealing with mixed number/character input + --INI-- + precision=14 + --FILE-- + + ===DONE=== + + --EXPECTF-- + 23abc :-33 float(40.224370722238) + ===DONE=== +``` + +If executed as PHP script the output will stop after the code on the `--FILE--` section has been +run. diff --git a/docs/source/testing/writing-tests/samples/index.md b/docs/source/testing/writing-tests/samples/index.md new file mode 100644 index 000000000000..10ab81f716fe --- /dev/null +++ b/docs/source/testing/writing-tests/samples/index.md @@ -0,0 +1,1194 @@ +# Samples + +## capture_stdio_1.phpt + +```php + + --TEST-- + Test covering the I/O stdin and stdout streams. + --DESCRIPTION-- + This tests checks if the output of stdin and stdout I/O streams match the + expected content. + --CAPTURE_STDIO-- + STDIN STDERR + --FILE-- + + --EXPECT-- + This is error sent to the stderr I/O stream +``` + +## capture_stdio_2.phpt + +```php + + --TEST-- + Test covering the I/O stdin and stderr streams. + --DESCRIPTION-- + This tests checks if the output of stdin and stderr I/O streams match the + expected content. + --CAPTURE_STDIO-- + STDIN STDOUT + --FILE-- + + --EXPECT-- + Hello, world. This is sent to the stdout I/O stream +``` + +## capture_stdio_3.phpt + +```php + + --TEST-- + Test covering the all standard I/O streams. + --DESCRIPTION-- + This tests checks if the output of stdin, stdout and stderr I/O streams match + the expected content. + --CAPTURE_STDIO-- + STDIN STDOUT STDERR + --FILE-- + + --EXPECT-- + Hello, world. This is sent to the stdout I/O stream + This is error sent to the stderr I/O stream +``` + +## clean.php + +```php + + Nmsgs; $i++) { + imap_delete($imap_stream, $i); + } + + $mailboxes = imap_getmailboxes($imap_stream, $server, '*'); + + foreach($mailboxes as $value) { + // Only delete mailboxes with our prefix + if (preg_match('/\{.*?\}INBOX\.(.+)/', $value->name, $match) == 1) { + if (strlen($match[1]) >= strlen($mailbox_prefix) + && substr_compare($match[1], $mailbox_prefix, 0, strlen($mailbox_prefix)) == 0) { + imap_deletemailbox($imap_stream, $value->name); + } + } + } + + imap_close($imap_stream, CL_EXPUNGE); + ?> +``` + +## conflicts_1.phpt + +```php + + --TEST-- + Test get_headers() function : test with context + --CONFLICTS-- + server + --FILE-- + array( + 'method' => 'HEAD' + ) + ); + + $context = stream_context_create($opts); + $headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1, $context); + echo $headers["X-Request-Method"]."\n"; + + stream_context_set_default($opts); + $headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1); + echo $headers["X-Request-Method"]."\n"; + + echo "Done"; + ?> + --EXPECT-- + HEAD + HEAD + Done +``` + +## extensions.phpt + +```php + + --TEST-- + phpt EXTENSIONS directive with shared extensions + --DESCRIPTION-- + This test covers the presence of some loaded extensions with a list of additional + extensions to be loaded when running test. + --EXTENSIONS-- + curl + imagick + tokenizer + --FILE-- + + --EXPECT-- + bool(true) + bool(true) + bool(true) +``` + +## file012.phpt + +```php + + +``` + +## phpdbg_1.phpt + +```php + + --TEST-- + Test deleting breakpoints + --PHPDBG-- + b 4 + b del 0 + b 5 + r + b del 1 + r + y + q + --EXPECTF-- + [Successful compilation of %s] + prompt> [Breakpoint #0 added at %s:4] + prompt> [Deleted breakpoint #0] + prompt> [Breakpoint #1 added at %s:5] + prompt> 12 + [Breakpoint #1 at %s:5, hits: 1] + >00005: echo $i++; + 00006: echo $i++; + 00007: + prompt> [Deleted breakpoint #1] + prompt> Do you really want to restart execution? (type y or n): 1234 + [Script ended normally] + prompt> + --FILE-- + + --INI-- + precision=14 + --SKIPIF-- + + --GET-- + a=test&b=https://example.com + --POST-- + c=

string

&d=12345.7 + --FILE-- + + --EXPECTF-- + string(4) "test" + string(19) "https://example.com" + string(27) "<b>test</b>" + + Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d + bool(false) + string(6) "string" + float(12345.7) + string(29) "<p>string</p>" + bool(false) + + Warning: filter_var() expects parameter 2 to be long, string given in %ssample001.php on line %d + NULL + + Warning: filter_input() expects parameter 3 to be long, string given in %ssample001.php on line %d + NULL + + Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d + NULL + + Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d + NULL + Done +``` + +## sample002.phpt + +```php + + --TEST-- + Test receipt of cookie data. + --CREDITS-- + Zoe Slattery zoe@php.net + # TestFest Munich 2009-05-19 + --COOKIE-- + hello=World;goodbye=MrChips + --FILE-- + + --EXPECT-- + array(2) { + ["hello"]=> + string(5) "World" + ["goodbye"]=> + string(7) "MrChips" + } +``` + +## sample003.phpt + +```php + + --TEST-- + session object deserialization + --SKIPIF-- + + --INI-- + session.use_cookies=0 + session.cache_limiter= + register_globals=1 + session.serialize_handler=php + session.save_handler=files + --FILE-- + yes++; } + } + + session_id("abtest"); + session_start(); + session_decode('baz|O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}arr|a:1:{i:3;O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}}'); + + $baz->method(); + $arr[3]->method(); + + var_dump($baz); + var_dump($arr); + session_destroy(); + --EXPECT-- + object(foo)#1 (2) { + ["bar"]=> + string(2) "ok" + ["yes"]=> + int(2) + } + array(1) { + [3]=> + object(foo)#2 (2) { + ["bar"]=> + string(2) "ok" + ["yes"]=> + int(2) + } + } +``` + +## sample005.phpt + +```php + + --TEST-- + SOAP Server 19: compressed request (gzip) + --SKIPIF-- + + --INI-- + precision=14 + --GZIP_POST-- + + + + + + --FILE-- + "http://testuri.org")); + $server->addfunction("test"); + $server->handle(); + echo "ok\n"; + ?> + --EXPECT-- + + Hello World + ok +``` + +## sample006.phpt + +```php + + --TEST-- + is_uploaded_file() function + --CREDITS-- + Dave Kelsey + --SKIPIF-- + + --POST_RAW-- + Content-type: multipart/form-data, boundary=AaB03x + + --AaB03x + content-disposition: form-data; name="field1" + + Joe Blow + --AaB03x + content-disposition: form-data; name="pics"; filename="file1.txt" + Content-Type: text/plain + + abcdef123456789 + --AaB03x-- + --FILE-- + + --EXPECTF-- + bool(true) + bool(false) + bool(false) + bool(false) + + Warning: is_uploaded_file() expects exactly 1 parameter, 0 given in %s on line %d + NULL + + Warning: is_uploaded_file() expects exactly 1 parameter, 2 given in %s on line %d + NULL +``` + +## sample007.phpt + +```php + + --TEST-- + SOAP Server 20: compressed request (deflate) + --SKIPIF-- + + --INI-- + precision=14 + --DEFLATE_POST-- + + + + + + + --FILE-- + "http://testuri.org")); + $server->addfunction("test"); + $server->handle(); + echo "ok\n"; + ?> + --EXPECT-- + + Hello World + ok +``` + +## sample008.phpt + +```php + + --TEST-- + GET/POST/REQUEST Test with input_filter + --SKIPIF-- + + --POST-- + d=379 + --GET-- + ar[elm1]=1234&ar[elm2]=0660&a=0234 + --FILE-- + FILTER_FLAG_ALLOW_OCTAL)); + var_dump($ret); + + $ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_REQUIRE_ARRAY)); + var_dump($ret); + + $ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_FLAG_ALLOW_OCTAL|FILTER_REQUIRE_ARRAY)); + var_dump($ret); + + ?> + --EXPECT-- + bool(false) + int(156) + array(2) { + ["elm1"]=> + int(1234) + ["elm2"]=> + bool(false) + } + array(2) { + ["elm1"]=> + int(1234) + ["elm2"]=> + int(432) + } +``` + +## sample009.phpt + +```php + + --TEST-- + STDIN input + --FILE-- + + --STDIN-- + fooBar + use this to input some thing to the php script + --EXPECT-- + string(54) "fooBar + use this to input some thing to the php script + " +``` + +## sample010.phpt + +```php + + --TEST-- + getopt#005 (Required values) + --ARGS-- + --arg value --arg=value -avalue -a=value -a value + --INI-- + register_argc_argv=On + variables_order=GPS + --FILE-- + + --EXPECT-- + array(2) { + ["arg"]=> + array(2) { + [0]=> + string(5) "value" + [1]=> + string(5) "value" + } + ["a"]=> + array(3) { + [0]=> + string(5) "value" + [1]=> + string(5) "value" + [2]=> + string(5) "value" + } + } +``` + +## sample011.phpt + +```php + + --TEST-- + Bug #35382 (Comment in end of file produces fatal error) + --FILEEOF-- + + --REDIRECTTEST-- + return array( + 'ENV' => array( + 'PDOTEST_DSN' => 'sqlite2::memory:' + ), + 'TESTS' => 'ext/pdo/tests' + ); +``` + +## sample014.phpt + +```php + + --TEST-- + MySQL + --SKIPIF-- + + --REDIRECTTEST-- + # magic auto-configuration + + $config = array( + 'TESTS' => 'ext/pdo/tests' + ); + + if (false !== getenv('PDO_MYSQL_TEST_DSN')) { + # user set them from their shell + $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN'); + $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER'); + $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS'); + if (false !== getenv('PDO_MYSQL_TEST_ATTR')) { + $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR'); + } + } else { + $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test'; + $config['ENV']['PDOTEST_USER'] = 'root'; + $config['ENV']['PDOTEST_PASS'] = ''; + } + + return $config; +``` + +## sample016.phpt + +```php + + --TEST-- + Test get variables with CGI binary + --GET-- + hello=World&goodbye=MrChips + --CGI-- + --FILE-- + + --EXPECT-- + array(2) { + ["hello"]=> + string(5) "World" + ["goodbye"]=> + string(7) "MrChips" + } +``` + +## sample017.phpt + +```php + + --TEST-- + PDO Common: Bug #34630 (inserting streams as LOBs) + --SKIPIF-- + + --FILE-- + getAttribute(PDO::ATTR_DRIVER_NAME); + $is_oci = $driver == 'oci'; + + if ($is_oci) { + $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val BLOB)'); + } else { + $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val VARCHAR(256))'); + } + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $fp = tmpfile(); + fwrite($fp, "I am the LOB data"); + rewind($fp); + + if ($is_oci) { + /* oracle is a bit different; you need to initiate a transaction otherwise + * the empty blob will be committed implicitly when the statement is + * executed */ + $db->beginTransaction(); + $insert = $db->prepare("insert into test (id, val) values (1, EMPTY_BLOB()) RETURNING val INTO :blob"); + } else { + $insert = $db->prepare("insert into test (id, val) values (1, :blob)"); + } + $insert->bindValue(':blob', $fp, PDO::PARAM_LOB); + $insert->execute(); + $insert = null; + + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + var_dump($db->query("SELECT * from test")->fetchAll(PDO::FETCH_ASSOC)); + + ?> + --XFAIL-- + This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64 + --EXPECT-- + array(1) { + [0]=> + array(2) { + ["id"]=> + string(1) "1" + ["val"]=> + string(17) "I am the LOB data" + } + } +``` + +## sample018.phpt + +```php + + --TEST-- + Phar front controller rewrite access denied [cache_list] + --INI-- + default_charset=UTF-8 + phar.cache_list={PWD}/frontcontroller10.php + --SKIPIF-- + + --ENV-- + SCRIPT_NAME=/frontcontroller10.php + REQUEST_URI=/frontcontroller10.php/hi + PATH_INFO=/hi + --FILE_EXTERNAL-- + files/frontcontroller4.phar + --EXPECTHEADERS-- + Content-type: text/html; charset=UTF-8 + Status: 403 Access Denied + --EXPECT-- + + + Access Denied + + +

403 - File /hi Access Denied

+ + +``` + +## sample019.phpt + +```php + + --TEST-- + bzopen() and invalid parameters + --SKIPIF-- + + --FILE-- + + --EXPECTF-- + Warning: bzopen() expects exactly 2 parameters, 0 given in %s on line %d + NULL + + Warning: bzopen(): '' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d + bool(false) + + Warning: bzopen(): filename cannot be empty in %s on line %d + bool(false) + + Warning: bzopen(): filename cannot be empty in %s on line %d + bool(false) + + Warning: bzopen(): 'x' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d + bool(false) + + Warning: bzopen(): 'rw' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d + bool(false) + + Warning: bzopen(no_such_file): failed to open stream: No such file or directory in %s on line %d + bool(false) + resource(%d) of type (stream) + Done +``` + +## sample020.phpt + +```php + + --TEST-- + Bug #42082 (NodeList length zero should be empty) + --FILE-- + query('*'); + var_dump($nodes); + var_dump($nodes->length); + $length = $nodes->length; + var_dump(empty($nodes->length), empty($length)); + + $doc->loadXML(""); + var_dump($doc->firstChild->nodeValue, empty($doc->firstChild->nodeValue), isset($doc->firstChild->nodeValue)); + var_dump(empty($doc->nodeType), empty($doc->firstChild->nodeType)) + ?> + --EXPECTF-- + object(DOMNodeList)#%d (0) { + } + int(0) + bool(true) + bool(true) + string(0) "" + bool(true) + bool(true) + bool(false) + bool(false) +``` + +## sample021.phpt + +```php + + --TEST-- + Math constants + --INI-- + precision=14 + --FILE-- + + --EXPECTREGEX-- + M_E : 2.718281[0-9]* + M_LOG2E : 1.442695[0-9]* + M_LOG10E : 0.434294[0-9]* + M_LN2 : 0.693147[0-9]* + M_LN10 : 2.302585[0-9]* + M_PI : 3.141592[0-9]* + M_PI_2 : 1.570796[0-9]* + M_PI_4 : 0.785398[0-9]* + M_1_PI : 0.318309[0-9]* + M_2_PI : 0.636619[0-9]* + M_SQRTPI : 1.772453[0-9]* + M_2_SQRTPI: 1.128379[0-9]* + M_LNPI : 1.144729[0-9]* + M_EULER : 0.577215[0-9]* + M_SQRT2 : 1.414213[0-9]* + M_SQRT1_2 : 0.707106[0-9]* + M_SQRT3 : 1.732050[0-9]* +``` + +## sample022.phpt + +```php + + --TEST-- + shm_detach() tests + --SKIPIF-- + + --FILE-- + + --CLEAN-- + + --EXPECTF-- + Warning: shm_detach() expects exactly 1 parameter, 0 given in %ssample022.php on line %d + NULL + + Warning: shm_detach() expects exactly 1 parameter, 2 given in %ssample022.php on line %d + NULL + bool(true) + + Warning: shm_detach(): %d is not a valid sysvshm resource in %ssample022.php on line %d + bool(false) + + Warning: shm_remove(): %d is not a valid sysvshm resource in %ssample022.php on line %d + + Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d + NULL + + Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d + NULL + + Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d + NULL + Done +``` + +## sample023.phpt + +```php + + --TEST-- + Bug #23894 (sprintf() decimal specifiers problem) + --FILE-- + + --EXPECTREGEX-- + string\(4\) \"-012\" + string\(8\) \"2d303132\" + (string\(13\) \" 4294967284\"|string\(20\) \"18446744073709551604\") + (string\(26\) \"20202034323934393637323834\"|string\(40\) \"3138343436373434303733373039353531363034\") +``` + +## sample024.phpt + +```php + + --TEST-- + DOMDocument::save Test basic function of save method + --SKIPIF-- + + --FILE-- + formatOutput = true; + + $root = $doc->createElement('book'); + + $root = $doc->appendChild($root); + + $title = $doc->createElement('title'); + $title = $root->appendChild($title); + + $text = $doc->createTextNode('This is the title'); + $text = $title->appendChild($text); + + $temp_filename = __DIR__.'/DomDocument_save_basic.tmp'; + + echo 'Wrote: ' . $doc->save($temp_filename) . ' bytes'; // Wrote: 72 bytes + ?> + --CLEAN-- + + --EXPECTF-- + Wrote: 72 bytes +``` + +## sample025.phpt + +```php + + --TEST-- + Test imap_append() function : basic functionality + --SKIPIF-- + + --FILE-- + Mailbox . "\n"; + var_dump(imap_append($imap_stream, $mb_details->Mailbox + , "From: webmaster@something.com\r\n" + . "To: info@something.com\r\n" + . "Subject: Test message\r\n" + . "\r\n" + . "this is a test message, please ignore\r\n" + )); + + var_dump(imap_append($imap_stream, $mb_details->Mailbox + , "From: webmaster@something.com\r\n" + . "To: info@something.com\r\n" + . "Subject: Another test\r\n" + . "\r\n" + . "this is another test message, please ignore it too!!\r\n" + )); + + $check = imap_check($imap_stream); + echo "Msg Count after append : ". $check->Nmsgs . "\n"; + + echo "List the msg headers\n"; + var_dump(imap_headers($imap_stream)); + + imap_close($imap_stream); + ?> + --CLEAN-- + + --EXPECTF-- + *** Testing imap_append() : basic functionality *** + Create a new mailbox for test + Create a temporary mailbox and add 0 msgs + .. mailbox '%s' created + Add a couple of msgs to new mailbox {%s}INBOX.%s + bool(true) + bool(true) + Msg Count after append : 2 + List the msg headers + array(2) { + [0]=> + string(%d) "%w%s 1)%s webmaster@something. Test message (%d chars)" + [1]=> + string(%d) "%w%s 2)%s webmaster@something. Another test (%d chars)" + } +``` + +## sample026.phpt + +```php + + --TEST-- + SPL: ArrayIterator implementing RecursiveIterator + --FILE-- + array(21, 22 => array(221, 222), 23 => array(231)), 3); + + $dir = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::LEAVES_ONLY); + + foreach ($dir as $file) { + print "$file\n"; + } + + ?> + ===DONE=== + + --EXPECT-- + 1 + 21 + 221 + 222 + 231 + 3 +``` + +## skipif2.phpt + +```php + + +``` + +## skipif.phpt + +```php + + +``` + +## xfailif.phpt + +```php + + --TEST-- + Handling of errors during linking + --INI-- + opcache.enable=1 + opcache.enable_cli=1 + opcache.optimization_level=-1 + opcache.preload={PWD}/preload_inheritance_error_ind.inc + --SKIPIF-- + + --FILE-- + + --EXPECTF-- + Fatal error: Declaration of B::foo($bar) must be compatible with A::foo() in %spreload_inheritance_error.inc on line 8 +``` diff --git a/docs/source/testing/writing-tests/sections/index.md b/docs/source/testing/writing-tests/sections/index.md new file mode 100644 index 000000000000..3a201e07d264 --- /dev/null +++ b/docs/source/testing/writing-tests/sections/index.md @@ -0,0 +1,1247 @@ +# Sections + +A phpt test can have many more parts than just the minimum. In fact some of the mandatory parts have +alternatives that may be used if the situation warrants it. The phpt sections are documented here. + +## `--TEST--` + +**Description:** Title of test as a single line short description. + +**Required:** Yes + +**Format:** Plain text. We recommend a single line only. + +Example 1 (snippet): + +```text + + --TEST-- + Test filter_input() with GET and POST data. +``` + +Example 1 (full): {ref}`sample001.phpt` + +## `--DESCRIPTION--` + +**Description:** If your test requires more than a single line title to adequately describe it, you +can use this section for further explanation. Multiple lines are allowed and besides being used for +information, this section is completely ignored by the test binary. + +**Required:** No + +**Format:** Plain text, multiple lines. + +Example 1 (snippet): + +```text + + --DESCRIPTION-- + This test covers both valid and invalid usages of filter_input() with INPUT_GET and INPUT_POST data and several different filter sanitizers. +``` + +Example 1 (full): {ref}`sample001.phpt` + +## `--CREDITS--` + +**Description:** Used to credit contributors without CVS commit rights, who put their name and email +on the first line. If the test was part of a TestFest event, then # followed by the name of the +event and the date (YYYY-MM-DD) on the second line. + +**Required:** No. For newly created tests the section should no longer be used for simple authorship +claims or listing all contributors who edited the test; as it is already accurately tracked by Git. +It may be used if more specific attribution is useful, for example to credit the original reporter +of a bug or a contributor who is not credited via `Co-authored-by` tag. + +**Format:** Name Email [Event] + +Example 1 (snippet): + +```text + + --CREDITS-- + Felipe Pena +``` + +Example 1 (full): {ref}`sample001.phpt` + +Example 2 (snippet): + +```text + + --CREDITS-- + Zoe Slattery zoe@php.net + # TestFest Munich 2009-05-19 +``` + +Example 2 (full): {ref}`sample002.phpt` + +## `--SKIPIF--` + +**Description:** A condition or set of conditions used to determine if a test should be skipped. +Tests that are only applicable to a certain platform, extension or PHP version are good reasons for +using a `--SKIPIF--` section. + +A common practice for extension tests is to write your `--SKIPIF--` extension criteria into a file +call skipif.inc and then including that file in the `--SKIPIF--` section of all your extension +tests. This promotes the DRY principle and reduces future code maintenance. + +**Required:** No. + +**Format:** PHP code enclosed by PHP tags. If the output of this scripts starts with "skip", the +test is skipped. If the output starts with "xfail", the test is marked as expected failure. If the +output starts with "flaky", the test is marked as flaky test. The "xfail" convention is supported as +of PHP 7.2.0. The "flaky" convention is supported as of PHP 8.2.25 and PHP 8.3.13, respectively. + +Example 1 (snippet): + +```php + + --SKIPIF-- + +``` + +Example 1 (full): {ref}`sample001.phpt` + +Example 2 (snippet): + +```php + + --SKIPIF-- + +``` + +Example 2 (full): {ref}`sample003.phpt` + +Example 3 (snippet): + +```php + + --SKIPIF-- + +``` + +Example 3 (full): {ref}`xfailif.phpt` + +Example 4 (snippet): + +```php + + --SKIPIF-- + + --FILE-- + [snip] +``` + +Test script and `SKIPIF` code should be directly written into `\*.phpt`. However, it is +recommended to use include files when more test scripts depend on the same `SKIPIF` code or when +certain test files need the same values for some input. + +> [!NOTE] +> No file used by any test should have one of the following extensions: ".php", ".log", ".mem", +> ".exp", ".out" or ".diff". When you use an include file for the `SKIPIF` section it should be +> named "skipif.inc" and an include file used in the `FILE` section of many tests should be named +> "test.inc". + +## `--CONFLICTS--` + +**Description:** This section is only relevant for parallel test execution (available as of PHP +7.4.0), and allows to specify conflict keys. While a test that conflicts with key K is running, no +other test that conflicts with K is run. For tests conflicting with "all", no other tests are run in +parallel. + +An alternative to have a `--CONFLICTS--` section is to add a file named `CONFLICTS` to the +directory containing the tests. The contents of the `CONFLICTS` file must have the same format as +the contents of the `--CONFLICTS--` section. + +**Required:** No. + +**Format:** One conflict key per line. Comment lines starting with # are also allowed. + +Example 1 (snippet): + +```text + + --CONFLICTS-- + server +``` + +Example 1 (full): {ref}`conflicts_1.phpt` + +## `--WHITESPACE_SENSITIVE--` + +**Description:** This flag is used to indicate that the test should not be changed by automated +formatting changes. Available as of PHP 7.4.3. + +**Required:** No. + +**Format:** No value, just the `--WHITESPACE_SENSITIVE--` statement. + +## `--CAPTURE_STDIO--` + +**Description:** This section enables which I/O streams the `run-tests.php` test script will use +when comparing executed file to the expected output. The `STDIN` is the standard input stream. +When `STDOUT` is enabled, the test script will also check the contents of the standard output. +`When STDERR` is enabled, the test script will also compare the contents of the standard error I/O +stream. + +If this section is left out of the test, by default, all three streams are enabled, so the tests +without this section capture all and is the same as enabling all three manually. + +**Required:** No. + +**Format:** A case insensitive space, newline or otherwise delimited list of one or more strings of +STDIN, STDOUT, and/or STDERR. + +Example 1 (snippet): + +```text + + --CAPTURE_STDIO-- + STDIN STDERR +``` + +Example 1 (full): {ref}`capture_stdio_1.phpt` + +Example 2 (snippet): + +```text + + --CAPTURE_STDIO-- + STDIN STDOUT +``` + +Example 2 (full): {ref}`capture_stdio_2.phpt` + +Example 3 (snippet): + +```text + + --CAPTURE_STDIO-- + STDIN STDOUT STDERR +``` + +Example 3(full): {ref}`capture_stdio_3.phpt` + +## `--EXTENSIONS--` + +**Description:** Additional required shared extensions to be loaded when running the test. When the +`run-tests.php` script is executed it loads all the extensions that are available and enabled for +that particular PHP at the time. If the test requires additional extension to be loaded and they +aren't loaded prior to running the test, this section loads them. + +**Required:** No. + +**Format:** A case sensitive newline separated list of extension names. + +Example 1 (snippet): + +```text + + --EXTENSIONS-- + curl + imagick + tokenizer +``` + +Example 1 (full): {ref}`extensions.phpt` + +Some tests depend on PHP extensions that may be unavailable. These extensions should be listed in +the `EXTENSIONS` section. If an extension is missing, PHP will try to find it in a shared module +and skip the test if it's not there. + +*/ext/sodium/tests/crypto_scalarmult.phpt* + +```php + + --TEST-- + Check for libsodium scalarmult + --EXTENSIONS-- + sodium + --FILE-- + string

&d=12345.7 +``` + +Example 1 (full): {ref}`sample001.phpt` + +Example 2 (snippet): + +```xml + + --POST-- + + + + + +``` + +Example 2 (full): {ref}`sample005.phpt` + +## `--POST_RAW--` + +**Description:** Raw POST data to be passed to the test script. This differs from the section above +because it doesn't automatically set the Content-Type, this leaves you free to define your own +within the section. This section forces the use of the CGI binary instead of the usual CLI one. + +**Required:** No. + +Requirements: PHP CGI binary. + +**Test Script Support:** `run-tests.php` + +**Format:** Follows the HTTP post data format. + +Example 1 (snippet): + +```text + + --POST_RAW-- + Content-type: multipart/form-data, boundary=AaB03x + + --AaB03x content-disposition: form-data; name="field1" + + Joe Blow + --AaB03x + content-disposition: form-data; name="pics"; filename="file1.txt" + Content-Type: text/plain + + abcdef123456789 + --AaB03x-- +``` + +Example 1 (full): {ref}`sample006.phpt` + +## `--PUT--` + +**Description:** Similar to the section above, PUT data to be passed to the test script. This +section forces the use of the CGI binary instead of the usual CLI one. + +**Required:** No. + +Requirements: PHP CGI binary. + +**Test Script Support:** `run-tests.php` + +**Format:** Raw data optionally preceded by a Content-Type header. + +Example 1 (snippet): + +```text + + --PUT-- + Content-Type: text/json + + {"name":"default output handler","type":0,"flags":112,"level":0,"chunk_size":0,"buffer_size":16384,"buffer_used":3} +``` + +## `--GZIP_POST--` + +**Description:** When this section exists, the POST data will be gzencode()'d. This section forces +the use of the CGI binary instead of the usual CLI one. + +**Required:** No. + +**Test Script Support:** `run-tests.php` + +**Format:** Just add the content to be gzencode()'d in the section. + +Example 1 (snippet): + +```xml + + --GZIP_POST-- + + + + + +``` + +Example 1 (full): {ref}`sample005.phpt` + +## `--DEFLATE_POST--` + +**Description:** When this section exists, the POST data will be gzcompress()'ed. This section +forces the use of the CGI binary instead of the usual CLI one. + +**Required:** No. + +Requirements: + +**Test Script Support:** `run-tests.php` + +**Format:** Just add the content to be gzcompress()'ed in the section. + +Example 1 (snippet): + +```xml + + --DEFLATE_POST-- + + + + + + +``` + +Example 1 (full): {ref}`sample007.phpt` + +## `--GET--` + +**Description:** GET variables to be passed to the test script. This section forces the use of the +CGI binary instead of the usual CLI one. + +**Required:** No. + +Requirements: PHP CGI binary. + +**Format:** A single line of text passed as the GET data to the script. + +Example 1 (snippet): + +```text + + --GET-- + a=test&b=http://example.com +``` + +Example 1 (full): {ref}`sample001.phpt` + +Example 2 (snippet): + +```text + + --GET-- + ar[elm1]=1234&ar[elm2]=0660&a=0234 +``` + +Example 2 (full): {ref}`sample008.phpt` + +## `--COOKIE--` + +**Description:** Cookies to be passed to the test script. This section forces the use of the CGI +binary instead of the usual CLI one. + +**Required:** No. + +Requirements: PHP CGI binary. + +**Test Script Support:** `run-tests.php` + +**Format:** A single line of text in a valid HTTP cookie format. + +Example 1 (snippet): + +``` + + --COOKIE-- + hello=World;goodbye=MrChips +``` + +Example 1 (full): {ref}`sample002.phpt` + +## `--STDIN--` + +**Description:** Data to be fed to the test script's standard input. + +**Required:** No. + +**Test Script Support:** `run-tests.php` + +**Format:** Any text within this section is passed as STDIN to PHP. + +Example 1 (snippet): + +```text + + --STDIN-- + fooBar + use this to input some thing to the php script +``` + +Example 1 (full): {ref}`sample009.phpt` + +## `--INI--` + +**Description:** To be used if you need a specific php.ini setting for the test. + +**Required:** No. + +**Format:** Key value pairs including automatically replaced tags. One setting per line. Content +that is not a valid ini setting may cause failures. + +The following is a list of all tags and what they are used to represent: + +- `{PWD}`: Represents the directory of the file containing the `--INI--` section. +- `{TMP}`: Represents the system's temporary directory. Available as of PHP 7.2.19 and 7.3.6. + +Example 1 (snippet): + +```text + + --INI-- + precision=14 +``` + +Example 1 (full): {ref}`sample001.phpt` + +Example 2 (snippet): + +```text + + --INI-- + session.use_cookies=0 + session.cache_limiter= + register_globals=1 + session.serialize_handler=php + session.save_handler=files +``` + +Example 2 (full): {ref}`sample003.phpt` + +## `--ARGS--` + +**Description:** A single line defining the arguments passed to PHP. + +**Required:** No. + +**Format:** A single line of text that is passed as the argument(s) to the PHP CLI. + +Example 1 (snippet): + +```text + + --ARGS-- + --arg value --arg=value -avalue -a=value -a value +``` + +Example 1 (full): {ref}`sample010.phpt` + +## `--ENV--` + +**Description:** Configures environment variables such as those found in the `$_SERVER` global +array. + +**Required:** No. + +**Format:** Key value pairs. One setting per line. + +Example 1 (snippet): + +```text + + --ENV-- + SCRIPT_NAME=/frontcontroller10.php + REQUEST_URI=/frontcontroller10.php/hi + PATH_INFO=/hi +``` + +Example 1 (full): {ref}`sample018.phpt` + +## `--PHPDBG--` + +**Description:** This section takes arbitrary phpdbg commands and executes the test file according +to them as it would be run in the phpdbg prompt. + +**Required:** No. + +**Format:** Arbitrary phpdbg commands + +Example 1 (snippet): + +```text + + --PHPDBG-- + b + 4 + b + del + 0 + b + 5 + r + b + del + 1 + r + y + q +``` + +Example 1 (full): {ref}`phpdbg_1.phpt` + +## `--FILE--` + +**Description:** The test source code. + +**Required:** One of the `FILE` type sections is required. + +**Format:** PHP source code enclosed by PHP tags. + +Example 1 (snippet): + +```php + + --FILE-- + +``` + +Example 1 (full): {ref}`sample001.phpt` + +## `--FILEEOF--` + +**Description:** An alternative to `--FILE--` where any trailing line breaks (n || r || rn +found at the end of the section) are omitted. This is an extreme edge-case feature, so 99.99% of the +time you won't need this section. + +**Required:** One of the `FILE` type sections is required. + +**Test Script Support:** `run-tests.php` + +**Format:** PHP source code enclosed by PHP tags. + +Example 1 (snippet): + +```php + + --FILEEOF-- + array( + 'PDOTEST_DSN' => 'sqlite2::memory:' + ), + 'TESTS' => 'ext/pdo/tests' + ); +``` + +Example 1 (full): {ref}`sample013.phpt` + +> [!NOTE] +> The destination tests for this example are not included. See the PDO extension tests for +> reference to live tests using this section. + +Example 2 (snippet): + +```php + + --REDIRECTTEST-- + # magic auto-configuration + + $config = array( + 'TESTS' => 'ext/pdo/tests' + ); + + if (false !== getenv('PDO_MYSQL_TEST_DSN')) { + # user set them from their shell + $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN'); + $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER'); + $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS'); + if (false !== getenv('PDO_MYSQL_TEST_ATTR')) { + $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR'); + } + } else { + $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test'; + $config['ENV']['PDOTEST_USER'] = 'root'; + $config['ENV']['PDOTEST_PASS'] = ''; + } + + return $config; +``` + +Example 2 (full): {ref}`sample014.phpt` + +> [!NOTE] +> The destination tests for this example are not included. See the PDO extension tests for +> reference to live tests using this section. + +## `--CGI--` + +**Description:** This section takes no value. It merely provides a simple marker for tests that MUST +be run as CGI, even if there is no `--POST--` or `--GET--` sections in the test file. + +**Required:** No. + +**Format:** No value, just the `--CGI--` statement. + +Example 1 (snippet): + +```text + + --CGI-- +``` + +Example 1 (full): {ref}`sample016.phpt` + +## `--XFAIL--` + +**Description:** This section identifies this test as one that is currently expected to fail. It +should include a brief description of why it's expected to fail. Reasons for such expectations +include tests that are written before the functionality they are testing is implemented or notice of +a bug which is due to upstream code such as an extension which provides PHP support for some other +software. + +Please do NOT include an `--XFAIL--` without providing a text description for the reason it's +being used. + +**Required:** No. + +**Test Script Support:** `run-tests.php` + +**Format:** A short plain text description of why this test is currently expected to fail. + +Example 1 (snippet): + +```text + + --XFAIL-- + This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64 +``` + +Example 1 (full): {ref}`sample017.phpt` + +## `--FLAKY--` + +**Description:** This section identifies this test as one that occasionally fails. If the test +actually fails, it will be retried one more time, and that result will be reported. The section +should include a brief description of why the test is flaky. Reasons for this include tests that +rely on relatively precise timing, or temporary disc states. Available as of PHP 8.1.22 and 8.2.9, +respectively. + +Please do NOT include a `--FLAKY--` section without providing a text description for the reason it +is being used. + +**Required:** No. + +**Test Script Support:** `run-tests.php` + +**Format:** A short plain text description of why this test is flaky. + +Example 1 (snippet): + +``` + + --FLAKY-- + This test frequently fails in CI +``` + +Example 1 (full): flaky.phpt + +## `--EXPECTHEADERS--` + +**Description:** The expected headers. Any header specified here must exist in the response and have +the same value or the test fails. Additional headers found in the actual tests while running are +ignored. + +**Required:** No. + +**Format:** HTTP style headers. May include multiple lines. + +Example 1 (snippet): + +--EXPECTHEADERS-- Status: 404 + +Example 1 (snippet): + +```text + + --EXPECTHEADERS-- + Content-type: text/html; charset=UTF-8 + Status: 403 Access Denied +``` + +Example 1 (full): {ref}`sample018.phpt` + +> [!NOTE] +> The destination tests for this example are not included. See the phar extension tests for +> reference to live tests using this section. + +## `--EXPECT--` + +**Description:** The expected output from the test script. This must match the actual output from +the test script exactly for the test to pass. + +**Required:** One of the `EXPECT` type sections is required. + +**Format:** Plain text. Multiple lines of text are allowed. + +Example 1 (snippet): + +```text + + --EXPECT-- + array(2) { + ["hello"]=> + string(5) "World" + ["goodbye"]=> + string(7) "MrChips" + } +``` + +Example 1 (full): {ref}`sample002.phpt` + +## `--EXPECT_EXTERNAL--` + +**Description:** Similar to `--EXPECT--` section, but just stating a filename where to load the +expected output from. + +**Required:** One of the `EXPECT` type sections is required. + +**Test Script Support:** `run-tests.php` + +Example 1 (snippet): + +```text + + --EXPECT_EXTERNAL-- + test001.expected.txt +``` + +*test001.expected.txt* + +```php + + array(2) { + ["hello"]=> + string(5) "World" + ["goodbye"]=> + string(7) "MrChips" + } +``` + +## `--EXPECTF--` + +**Description:** An alternative of `--EXPECT--`. Where it differs from `--EXPECT--` is that it +uses a number of substitution tags for strings, spaces, digits, etc. that appear in test case output +but which may vary between test runs. The most common example of this is to use %s and %d to match +the file path and line number which are output by PHP Warnings. + +**Required:** One of the `EXPECT` type sections is required. + +**Format:** Plain text including tags which are inserted to represent different types of output +which are not guaranteed to have the same value on subsequent runs or when run on different +platforms. + +The following is a list of all tags and what they are used to represent: + +> - `%e`: Represents a directory separator, for example / on Linux. +> - `%s`: One or more of anything (character or white space) except the end of line character. +> - `%S`: Zero or more of anything (character or white space) except the end of line character. +> - `%a`: One or more of anything (character or white space) including the end of line +> character. +> - `%A`: Zero or more of anything (character or white space) including the end of line +> character. +> - `%w`: Zero or more white space characters. +> - `%i`: A signed integer value, for example +3142, -3142, 3142. +> - `%d`: An unsigned integer value, for example 123456. +> - `%x`: One or more hexadecimal character. That is, characters in the range 0-9, a-f, A-F. +> - `%f`: A floating point number, for example: 3.142, -3.142, 3.142E-10, 3.142e+10. +> - `%c`: A single character of any sort (.). +> - `%r...%r`: Any string (...) enclosed between two `%r` will be treated as a regular +> expression. + +Example 1 (snippet): + +```text + + --EXPECTF-- + string(4) "test" + string(18) "http://example.com" + string(27) "<b>test</b>" + + Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d + bool(false) + string(6) "string" + float(12345.7) + string(29) "<p>string</p>" + bool(false) + + Warning: filter_var() expects parameter 2 to be long, string given in %s011.php on line %d + NULL + + Warning: filter_input() expects parameter 3 to be long, string given in %s011.php on line %d + NULL + + Warning: filter_var() expects at most 3 parameters, 5 given in %s011.php on line %d + NULL + + Warning: filter_var() expects at most 3 parameters, 5 given in %s011.php on line %d + NULL + Done +``` + +Example 1 (full): {ref}`sample001.phpt` + +Example 2 (snippet): + +```text + + --EXPECTF-- + Warning: bzopen() expects exactly 2 parameters, 0 given in %s on line %d NULL + + Warning: bzopen(): '' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d + bool(false) + + Warning: bzopen(): filename cannot be empty in %s on line %d + bool(false) + + Warning: bzopen(): filename cannot be empty in %s on line %d + bool(false) + + Warning: bzopen(): 'x' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d + bool(false) + + Warning: bzopen(): 'rw' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d + bool(false) + + Warning: bzopen(no_such_file): failed to open stream: No such file or directory in %s on line %d + bool(false) + resource(%d) of type (stream) Done +``` + +Example 2 (full): {ref}`sample019.phpt` + +Example 3 (snippet): + +```text + + --EXPECTF-- + object(DOMNodeList)#%d (0) { + } + int(0) + bool(true) + bool(true) + string(0) "" + bool(true) + bool(true) + bool(false) + bool(false) +``` + +Example 2 (full): {ref}`sample020.phpt` + +`/ext/standard/tests/strings/str_shuffle.phpt` is a good example for using `EXPECTF` instead of +`EXPECT`. From time to time the algorithm used for shuffle changed and sometimes the machine used +to execute the code has influence on the result of shuffle. But it always returns a three character +string detectable by `%s` (that matches any string until the end of the line). Other scan-able +forms are `%a` for any amount of chars (at least one), `%i` for integers, `%d` for numbers +only, `%f` for floating point values, `%c` for single characters, `%x` for hexadecimal values, +`%w` for any number of whitespace characters and `%e` for `DIRECTORY_SEPARATOR` (`'\'` or +`'/'`). + +*/ext/standard/tests/strings/str_shuffle.phpt* + +```php + + --TEST-- + Testing str_shuffle. + --FILE-- + + --EXPECTF-- + string(3) "%s" + string(3) "123" +``` + +## `--EXPECTF_EXTERNAL--` + +**Description:** Similar to `--EXPECTF--` section, but like the `--EXPECT_EXTERNAL--` section +just stating a filename where to load the expected output from. + +**Required:** One of the `EXPECT` type sections is required. + +**Test Script Support:** `run-tests.php` + +## `--EXPECTREGEX--` + +**Description:** An alternative of `--EXPECT--`. This form allows the tester to specify the result +in a regular expression. + +**Required:** One of the `EXPECT` type sections is required. + +**Format:** Plain text including regular expression patterns which represent data that can vary +between subsequent runs of a test or when run on different platforms. + +Example 1 (snippet): + +```text + + --EXPECTREGEX-- + M_E : 2.718281[0-9]* + M_LOG2E : 1.442695[0-9]* + M_LOG10E : 0.434294[0-9]* + M_LN2 : 0.693147[0-9]* + M_LN10 : 2.302585[0-9]* + M_PI : 3.141592[0-9]* + M_PI_2 : 1.570796[0-9]* + M_PI_4 : 0.785398[0-9]* + M_1_PI : 0.318309[0-9]* + M_2_PI : 0.636619[0-9]* + M_SQRTPI : 1.772453[0-9]* + M_2_SQRTPI: 1.128379[0-9]* + M_LNPI : 1.144729[0-9]* + M_EULER : 0.577215[0-9]* + M_SQRT2 : 1.414213[0-9]* + M_SQRT1_2 : 0.707106[0-9]* + M_SQRT3 : 1.732050[0-9]* +``` + +Example 1 (full): {ref}`sample021.phpt` + +Example 2 (snippet): + +```text + + --EXPECTF-- + *** Testing imap_append() : basic functionality *** + Create a new mailbox for test + Create a temporary mailbox and add 0 msgs + .. mailbox '%s' created + Add a couple of msgs to new mailbox {%s}INBOX.%s + bool(true) + bool(true) + Msg Count after append : 2 + List the msg headers + array(2) { + [0]=> + string(%d) "%w%s 1)%s webmaster@something. Test message (%d chars)" + [1]=> + string(%d) "%w%s 2)%s webmaster@something. Another test (%d chars)" + } +``` + +Example 2 (full): {ref}`sample025.phpt` + +Example 3 (snippet): + +```text + + --EXPECTREGEX-- + string\(4\) \"-012\" + string\(8\) \"2d303132\" + (string\(13\) \" 4294967284\"|string\(20\) \"18446744073709551604\") + (string\(26\) \"20202034323934393637323834\"|string\(40\) \"3138343436373434303733373039353531363034\") +``` + +Example 3 (full): {ref}`sample023.phpt` + +`/ext/standard/tests/strings/strings001.phpt` is a good example for using `EXPECTREGEX` instead +of `EXPECT`. This test also shows that in `EXPECTREGEX` some characters need to be escaped since +otherwise they would be interpreted as a regular expression. + +*/ext/standard/tests/strings/strings001.phpt* + +```php + + --TEST-- + Test whether strstr() and strrchr() are binary safe. + --FILE-- + + --EXPECTREGEX-- + string\(18\) \"nica\x00turska panica\" + string\(19\) \" nica\x00turska panica\" +``` + +## `--EXPECTREGEX_EXTERNAL--` + +**Description:** Similar to `--EXPECTREGEX--` section, but like the `--EXPECT_EXTERNAL--` +section just stating a filename where to load the expected output from. + +**Required:** One of the `EXPECT` type sections is required. + +**Test Script Support:** `run-tests.php` + +## `--CLEAN--` + +**Description:** Code that is executed after a test completes. It's main purpose is to allow you to +clean up after yourself. You might need to remove files created during the test or close sockets or +database connections following a test. Infact, even if a test fails or encounters a fatal error +during the test, the code found in the `--CLEAN--` section will still run. + +Code in the clean section is run in a completely different process than the one the test was run in. +So do not try accessing variables you created in the `--FILE--` section from inside the +`--CLEAN--` section, they won't exist. + +Using the switch `--no-clean` on `run-tests.php`, you can prevent the code found in the +`--CLEAN--` section of a test from running. This allows you to inspect generated data or files +without them being removed by the `--CLEAN--` section. + +**Required:** No. + +**Test Script Support:** `run-tests.php` + +**Format:** PHP source code enclosed by PHP tags. + +Example 1 (snippet): + +```php + + --CLEAN-- + +``` + +Example 1 (full): {ref}`sample024.phpt` + +Example 2 (snippet): + +```php + + --CLEAN-- + +``` + +Example 2 (full): {ref}`sample025.phpt` + +Example 3 (snippet): + +```php + + --CLEAN-- + +``` + +Example 3 (full): {ref}`sample022.phpt`