diff --git a/CMakeLists.txt b/CMakeLists.txt index bb810734202f6166338d9e02fced491dd6404396..cd0d4ab2137d3699b729c88689b102c335de63b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,8 @@ set(PROMOD3_VERSION_STRING ${PROMOD3_VERSION_MAJOR}.${PROMOD3_VERSION_MINOR}) set(PROMOD3_VERSION_STRING ${PROMOD3_VERSION_STRING}.${PROMOD3_VERSION_PATCH}) option(DISABLE_DOCUMENTATION "Do not build documentation" OFF) -option(DISABLE_DISABLE_DOCTEST "Do not check examples in documentation" OFF) -option(DISABLE_DISABLE_LINKCHECK "Do not check links in the documentation" OFF) +option(DISABLE_DOCTEST "Do not check examples in documentation" OFF) +option(DISABLE_LINKCHECK "Do not check links in the documentation" OFF) if(CMAKE_COMPILER_IS_GNUCXX) exec_program(gcc ARGS --version OUTPUT_VARIABLE CMAKE_C_COMPILER_VERSION) diff --git a/cmake_support/PROMOD3.cmake b/cmake_support/PROMOD3.cmake index a20c903399ded3d160ceb83003a22b295469adb3..6895e85fbac53477ccb6fc5cce6475f18fbc2159 100644 --- a/cmake_support/PROMOD3.cmake +++ b/cmake_support/PROMOD3.cmake @@ -674,7 +674,7 @@ add_dependencies(check codetest) #------------------------------------------------------------------------------- macro(promod3_unittest) set(_ARG_PREFIX promod3) - parse_argument_list(_ARG "MODULE;SOURCES;LINK;DATA;TARGET" "" ${ARGN}) + parse_argument_list(_ARG "MODULE;SOURCES;LINK;DATA;TARGET;BASE_TARGET" "" ${ARGN}) set(_SOURCES ${_ARG_SOURCES}) set(CPP_TESTS) set(PY_TESTS) @@ -752,7 +752,11 @@ macro(promod3_unittest) DEPENDS "_${_ARG_MODULE}" ${_test_name}) add_test("${_test_name}" ${CMAKE_CURRENT_BINARY_DIR}/${_test_name} ) add_dependencies(check_xml "${_test_name}_run_xml") - add_dependencies(codetest "${_test_name}_run") + if(_ARG_BASE_TARGET) + add_dependencies("${_ARG_BASE_TARGET}" "${_test_name}_run") + else() + add_dependencies(codetest "${_test_name}_run") + endif() if(_ARG_LINK) target_link_libraries("${_test_name}" ${_ARG_LINK}) @@ -763,37 +767,41 @@ macro(promod3_unittest) "${CMAKE_CURRENT_BINARY_DIR}") endif() - foreach(py_test ${PY_TESTS}) - set(py_twp "${CMAKE_CURRENT_SOURCE_DIR}/${py_test}") - if(NOT EXISTS "${py_twp}") - message(FATAL_ERROR "Python test script does not exist: ${py_twp}") - endif() - set(python_path $ENV{PYTHONPATH}) - if(python_path) - set(python_path "${python_path}:") - endif(python_path) - # we just add OST manually here until we find a more flexible way - set(python_path "${python_path}${LIB_STAGE_PATH}/${PYTHON_MODULE_PATH}:") - set(python_path "${python_path}${OST_PYMOD_PATH}") - set (PY_TESTS_CMD "PYTHONPATH=${python_path} ${PYTHON_BINARY}") - add_custom_target("${py_test}_run" - sh -c "${PY_TESTS_CMD} ${py_twp}" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "running checks ${py_test}" VERBATIM) - add_dependencies("${py_test}_run" "test_data_${_ARG_MODULE}") - add_dependencies("${py_test}_run" "_${_ARG_MODULE}") - # XML test outputgets an logical OR to 'echo' so if sth fails, make - # continues and we get output for all unit tests. Just calling 'echo' - # giveth $?=0. - add_custom_target("${py_test}_run_xml" - sh -c "${PY_TESTS_CMD} ${py_twp} xml || echo" - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "running checks ${py_test}" VERBATIM) - add_dependencies("${py_test}_run_xml" "test_data_${_ARG_MODULE}") - add_dependencies("${py_test}_run_xml" "_${_ARG_MODULE}") + foreach(py_test ${PY_TESTS}) + set(py_twp "${CMAKE_CURRENT_SOURCE_DIR}/${py_test}") + if(NOT EXISTS "${py_twp}") + message(FATAL_ERROR "Python test script does not exist: ${py_twp}") + endif() + set(python_path $ENV{PYTHONPATH}) + if(python_path) + set(python_path "${python_path}:") + endif(python_path) + # we just add OST manually here until we find a more flexible way + set(python_path "${python_path}${LIB_STAGE_PATH}/${PYTHON_MODULE_PATH}:") + set(python_path "${python_path}${OST_PYMOD_PATH}") + set (PY_TESTS_CMD "PYTHONPATH=${python_path} ${PYTHON_BINARY}") + add_custom_target("${py_test}_run" + sh -c "${PY_TESTS_CMD} ${py_twp}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "running checks ${py_test}" VERBATIM) + add_dependencies("${py_test}_run" "test_data_${_ARG_MODULE}") + add_dependencies("${py_test}_run" "_${_ARG_MODULE}") + # XML test outputgets an logical OR to 'echo' so if sth fails, make + # continues and we get output for all unit tests. Just calling 'echo' + # giveth $?=0. + add_custom_target("${py_test}_run_xml" + sh -c "${PY_TESTS_CMD} ${py_twp} xml || echo" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "running checks ${py_test}" VERBATIM) + add_dependencies("${py_test}_run_xml" "test_data_${_ARG_MODULE}") + add_dependencies("${py_test}_run_xml" "_${_ARG_MODULE}") + add_dependencies(check_xml "${py_test}_run_xml") + if(_ARG_BASE_TARGET) + add_dependencies("${_ARG_BASE_TARGET}" "${py_test}_run") + else() add_dependencies(codetest "${py_test}_run") - add_dependencies(check_xml "${py_test}_run_xml") - endforeach() + endif() + endforeach() endmacro(promod3_unittest) #------------------------------------------------------------------------------- diff --git a/cmake_support/doc/index.rst b/cmake_support/doc/index.rst index 508a5a5933d55094ba128a2631bc008c17f903e9..8325fe6fccc6f907f8e14482d604c812bd0f2be6 100644 --- a/cmake_support/doc/index.rst +++ b/cmake_support/doc/index.rst @@ -40,7 +40,8 @@ Unit Tests SOURCES source1 [source2 ...] [LINK library1/ linker flag1 [library2/ linker flag2 ...]] [DATA data1 [data2 ...]] - [TARGET target]) + [TARGET target] + [BASE_TARGET base_target]) Add unit tests to |project|. Unit tests should go in module-wise so all source files containing test code go by a single call of @@ -83,6 +84,11 @@ Unit Tests This defines an additional dependency for the unit test. That is, before running this unit test, this target will be built. + ``BASE_TARGET`` + This defines an alternative base target to which to add this unit test. + By default all unit tests are registered to be executed with the + ``codetest`` target. This can be overridden by using this argument. + Documentation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. cmake:command:: add_doc_source diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index e7baf70e3bad5b90cdf9fdd22b392e8122608e90..b2d033de4e5d2e3153d758eb44f40f46066c44b1 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -14,6 +14,9 @@ contributing.rst gettingstarted.rst ) +# add documentation tests (must be done before rest below!) +add_custom_target(doctest) +add_subdirectory(tests) # set up commands/ vars/ ... for the rst source files set(_RST_DEPS) @@ -106,6 +109,10 @@ foreach(mod ${PM3_PY_MS}) endforeach() endif() endforeach() + +# add dependencies from doctests +list(APPEND _DOC_MODULE_DEPS "${PM3_DOC_DEPS_doctests}") + # create targets for sphinx # for the html target, we make everything depend on index.html set(_SPHINX_HTML_DIR "${SHARED_DATA_PATH}/html") @@ -134,15 +141,6 @@ add_custom_target(doc ALL) add_dependencies(doc html) add_dependencies(doc man) -# doctest target, one is enough for all -set(_SPHINX_DOCTEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/doctest") -file(MAKE_DIRECTORY ${_SPHINX_DOCTEST_DIR}) -add_custom_target(doctest - COMMAND ${SPHINX_BINARY} -b doctest -c "${_RST_SOURCE_DIR}" - "${_RST_SOURCE_DIR}" "${_SPHINX_DOCTEST_DIR}" - DEPENDS "${_SPHINX_CONF_PY}" ${_RST_DEPS} ${_DOC_MODULE_DEPS} - ${PM3_PYMODULES} ${PM3_UNIT_TEST_DATA}) - # linkcheck target set(_SPHINX_LINKCHECK_DIR "${CMAKE_CURRENT_BINARY_DIR}/linkcheck") file(MAKE_DIRECTORY ${_SPHINX_LINKCHECK_DIR}) @@ -168,10 +166,3 @@ endif() install(DIRECTORY ${_SPHINX_HTML_DIR} DESTINATION "share/promod3") # install man pages install(DIRECTORY ${_SPHINX_MAN_DIR} DESTINATION "share/promod3") - -# TODO (GT): remove doctest - -# add documentation tests -add_subdirectory(tests) -# TODO (GT): put this in a good place and ensure doc is rebuilt when this changes... -add_dependencies(doc test_data_doc) \ No newline at end of file diff --git a/doc/buildsystem.rst b/doc/buildsystem.rst index 1e584dc22a886bca594ccd9c28a6a50851420c8a..449bdfb0a75f3da81f758b2a9986898b5316ae2c 100644 --- a/doc/buildsystem.rst +++ b/doc/buildsystem.rst @@ -105,8 +105,8 @@ Beside the usual ``make all`` and other default targets, there are a few special targets: * ``check`` :index:`make check` Runs unit tests and if |cmake| was invoked in - its standard configuration also |sphinx| with the ``doctest`` and - ``linkcheck`` builders + its standard configuration also documentation examples (``doctest``) and + links in the doc. (``linkcheck``) are checked * ``html`` :index:`make html` Creates documentation as web page using the |sphinx| ``html`` builder * ``man`` :index:`make man` Creates a manual page using the |sphinx| ``man`` diff --git a/doc/conf.py.in b/doc/conf.py.in index 7d7c5d4147565645161b87a0cbc91e47cf3097f6..1bb443cb0c0af3a627b02934e3b466f4c86224c0 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -33,10 +33,13 @@ sys.path.insert(3, r'@PROJECT_SOURCE_DIR@/actions/tests/') # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.pngmath', - 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.pngmath', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', 'sphinx.ext.extlinks', 'cmake'] # Check first line of docstring for signatures (autofunction) @@ -299,32 +302,4 @@ rst_epilog = """ # increase default timeout (5s) for linkcheck linkcheck_timeout = 15.0 -# global setup for doctests (will be executed before any testcode block) -doctest_global_setup = """ -# in some versions of sphinx, doctest invokes doctest_path AFTER executing code -# hence we set paths here... -import sys -sys.path.insert(0, '@LIB_STAGE_PATH@/@PYTHON_MODULE_PATH@') -sys.path.insert(1, '@OST_PYMOD_PATH@') - -import ost - -# We define a LogSink here, pushing everything to stdout so doctest recognises -# it. -class DocTestLogger(ost.LogSink): - def __init__(self): - ost.LogSink.__init__(self) - - def LogMessage(self, message, severity): - # there is a reason why we do not put 'severity' to any use in the message - # written to stdout. If we modify the message handed in by a doctest, - # verifying it would mean the creator of the doctest would have to check - # that very code here to get the test right. - sys.stdout.write('%s' % message) - -doctest_logger = DocTestLogger() -ost.PushLogSink(doctest_logger) -ost.PushVerbosityLevel(0) -""" - # LocalWords: cmake diff --git a/doc/contributing.rst b/doc/contributing.rst index dda02e86434a10efee8bf4645651bf17f8486b6b..09b704a057196ba0dd555335637b69322ff093a3 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -280,6 +280,40 @@ http://sphinx-doc.org/markup/inline.html If you write new functionality for |project|, or fix bugs, feel free to extend the Changelog. It will be automatically pulled into the documentation. +It is highly recommended to add code examples with your documentation. For that +purpose, you should write a fully runnable script, which is to be placed in the +:file:`doc/tests/scripts` directory. The script is to be runnable from within +the :file:`doc/tests` directory as ``pm SCRIPTPATH`` and may use data stored in +the :file:`doc/tests/data` directory. The script and any data needed by it, must +then be referenced in the :file:`doc/tests/CMakeLists.txt` file. Afterwards, +they can be included in the documentation using the +`literalinclude <http://www.sphinx-doc.org/en/stable/markup/code.html#includes>`_ +directive. For instance, if you add a new example code :file:`loop_main.py`, +you would add it in your module documentation as follows: + +.. code-block:: rest + + .. literalinclude:: ../../../tests/doc/scripts/loop_main.py + +If your example does not relate to a specific module and the documentation is +in the top-level :file:`doc` directory, you need to drop one of the ``..`` as +follows: + +.. code-block:: rest + + .. literalinclude:: ../../tests/doc/scripts/hello_world.py + +To ensure that the code examples keep on working, a unit test has to be defined +in :file:`doc/tests/test_doctests.py`. Each example code is run by a dedicated +test function. Usually, the codes are run and the return code is checked. +Command-line output or resulting files can also be checked (see existing test +codes for examples). A few more guidelines for example codes: + +- If it generates files as output, please delete them after checking them. +- If it requires external modules or binaries, check for their availablity. If + the external dependencies are not available, output a warning and skip the + test. + .. _how-to-start-your-own-module: -------------------------------------------------------------------------------- diff --git a/doc/tests/CMakeLists.txt b/doc/tests/CMakeLists.txt index f98ec1e0670cf2ce64aac49a192f302950f27a4c..baa2c336579517fdff7268d8fc9a3945d373f6a2 100644 --- a/doc/tests/CMakeLists.txt +++ b/doc/tests/CMakeLists.txt @@ -13,7 +13,9 @@ set(DOC_TEST_DATA data/1eye_rec.pdb data/gly.pdb data/structure_db_small.dat +) +set (DOC_TEST_SCRIPTS scripts/action_test.py scripts/action_test_verbose.py @@ -43,12 +45,21 @@ set(DOC_TEST_DATA scripts/sidechain_steps.py ) +# we add doc. test to separate target doctest (instead of codetest) promod3_unittest(MODULE doc SOURCES "${DOC_UNIT_TESTS}" - DATA "${DOC_TEST_DATA}" + DATA "${DOC_TEST_DATA};${DOC_TEST_SCRIPTS}" + BASE_TARGET doctest ) # extra manual copy of test_actions.py (as it is in different folder) copy_if_different("${CMAKE_CURRENT_SOURCE_DIR}/../../actions/tests" "${CMAKE_BINARY_DIR}/tests/doc/scripts" "test_actions.py" TARGETS "test_data_doc") + +# ensure that doc is rebuilt when examples change +set(_doctest_DEPS test_data_doc) +foreach(_file ${DOC_TEST_SCRIPTS}) + list(APPEND _doctest_DEPS "${CMAKE_CURRENT_SOURCE_DIR}/${_file}") +endforeach() +set(PM3_DOC_DEPS_doctests "${_doctest_DEPS}" CACHE INTERNAL "" FORCE)