| #----------------------------------------------------------------------------- | |
| # Function to run a child process and report output only on error. | |
| function(run_child) | |
| execute_process(${ARGN} | |
| RESULT_VARIABLE FAILED | |
| OUTPUT_VARIABLE OUTPUT | |
| ERROR_VARIABLE OUTPUT | |
| OUTPUT_STRIP_TRAILING_WHITESPACE | |
| ERROR_STRIP_TRAILING_WHITESPACE | |
| ) | |
| if(FAILED) | |
| string(REPLACE "\n" "\n " OUTPUT "${OUTPUT}") | |
| message(FATAL_ERROR "Child failed (${FAILED}), output is\n ${OUTPUT}\n" | |
| "Command = [${ARGN}]\n") | |
| endif() | |
| # Pass output back up to the parent scope for possible further inspection. | |
| set(OUTPUT "${OUTPUT}" PARENT_SCOPE) | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to find the Update.xml file and check for expected entries. | |
| function(check_updates build) | |
| # Find the Update.xml file for the given build tree | |
| set(PATTERN ${TOP}/${build}/Testing/*/Update.xml) | |
| file(GLOB UPDATE_XML_FILE RELATIVE ${TOP} ${PATTERN}) | |
| string(REGEX REPLACE "//Update.xml$" "/Update.xml" | |
| UPDATE_XML_FILE "${UPDATE_XML_FILE}" | |
| ) | |
| if(NOT UPDATE_XML_FILE) | |
| message(FATAL_ERROR "Cannot find Update.xml with pattern\n ${PATTERN}") | |
| endif() | |
| message(" found ${UPDATE_XML_FILE}") | |
| set(max_update_xml_size 16384) | |
| # Read entries from the Update.xml file | |
| set(types "Updated|Modified|Conflicting") | |
| file(STRINGS ${TOP}/${UPDATE_XML_FILE} UPDATE_XML_ENTRIES | |
| REGEX "<(${types}|FullName)>" | |
| LIMIT_INPUT ${max_update_xml_size} | |
| ) | |
| string(REGEX REPLACE | |
| "[ \t]*<(${types})>[ \t]*;[ \t]*<FullName>([^<]*)</FullName>" | |
| "\\1{\\2}" UPDATE_XML_ENTRIES "${UPDATE_XML_ENTRIES}") | |
| # If specified, remove the given prefix from the files in Update.xml. | |
| # Some VCS systems, like Perforce, return absolute locations | |
| if(DEFINED REPOSITORY_FILE_PREFIX) | |
| string(REPLACE | |
| "${REPOSITORY_FILE_PREFIX}" "" | |
| UPDATE_XML_ENTRIES "${UPDATE_XML_ENTRIES}") | |
| endif() | |
| # Compare expected and actual entries | |
| set(EXTRA "${UPDATE_XML_ENTRIES}") | |
| list(REMOVE_ITEM EXTRA ${ARGN} ${UPDATE_EXTRA} ${UPDATE_MAYBE}) | |
| set(MISSING "${ARGN}" ${UPDATE_EXTRA}) | |
| if(NOT "" STREQUAL "${UPDATE_XML_ENTRIES}") | |
| list(REMOVE_ITEM MISSING ${UPDATE_XML_ENTRIES}) | |
| endif() | |
| if(NOT UPDATE_NOT_GLOBAL) | |
| set(rev_elements Revision PriorRevision ${UPDATE_GLOBAL_ELEMENTS}) | |
| string(REPLACE ";" "|" rev_regex "${rev_elements}") | |
| set(rev_regex "^\t<(${rev_regex})>[^<\n]+</(${rev_regex})>$") | |
| file(STRINGS ${TOP}/${UPDATE_XML_FILE} UPDATE_XML_REVISIONS | |
| REGEX "${rev_regex}" | |
| LIMIT_INPUT ${max_update_xml_size} | |
| ) | |
| foreach(r IN LISTS UPDATE_XML_REVISIONS) | |
| string(REGEX REPLACE "${rev_regex}" "\\1" element "${r}") | |
| set(element_${element} 1) | |
| endforeach() | |
| foreach(element IN LISTS rev_elements) | |
| if(NOT element_${element}) | |
| list(APPEND MISSING "global <${element}> element") | |
| endif() | |
| endforeach() | |
| endif() | |
| # Report the result | |
| set(MSG "") | |
| if(MISSING) | |
| # List the missing entries | |
| string(APPEND MSG "Update.xml is missing expected entries:\n") | |
| foreach(f IN LISTS MISSING) | |
| string(APPEND MSG " ${f}\n") | |
| endforeach() | |
| else() | |
| # Success | |
| message(" no entries missing from Update.xml") | |
| endif() | |
| # Report the result | |
| if(EXTRA) | |
| # List the extra entries | |
| string(APPEND MSG "Update.xml has extra unexpected entries:\n") | |
| foreach(f IN LISTS EXTRA) | |
| string(APPEND MSG " ${f}\n") | |
| endforeach() | |
| else() | |
| # Success | |
| message(" no extra entries in Update.xml") | |
| endif() | |
| if(MSG) | |
| # Provide the log file | |
| file(GLOB UPDATE_LOG_FILE | |
| ${TOP}/${build}/Testing/Temporary/LastUpdate*.log) | |
| if(UPDATE_LOG_FILE) | |
| file(READ ${UPDATE_LOG_FILE} UPDATE_LOG LIMIT ${max_update_xml_size}) | |
| string(REPLACE "\n" "\n " UPDATE_LOG "${UPDATE_LOG}") | |
| string(APPEND MSG "Update log:\n ${UPDATE_LOG}") | |
| else() | |
| string(APPEND MSG "No update log found!") | |
| endif() | |
| # Display the error message | |
| message(FATAL_ERROR "${MSG}") | |
| endif() | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to create initial content. | |
| function(create_content dir) | |
| file(MAKE_DIRECTORY ${TOP}/${dir}) | |
| # An example CTest project configuration file. | |
| file(WRITE ${TOP}/${dir}/CTestConfig.cmake | |
| "# CTest Configuration File | |
| set(CTEST_NIGHTLY_START_TIME \"21:00:00 EDT\") | |
| ") | |
| # Some other files. | |
| file(WRITE ${TOP}/${dir}/foo.txt "foo\n") | |
| file(WRITE ${TOP}/${dir}/bar.txt "bar\n") | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to update content. | |
| function(update_content dir added_var removed_var dirs_var) | |
| file(APPEND ${TOP}/${dir}/foo.txt "foo line 2\n") | |
| file(WRITE ${TOP}/${dir}/zot.txt "zot\n") | |
| file(REMOVE ${TOP}/${dir}/bar.txt) | |
| file(MAKE_DIRECTORY ${TOP}/${dir}/subdir) | |
| file(WRITE ${TOP}/${dir}/subdir/foo.txt "foo\n") | |
| file(WRITE ${TOP}/${dir}/subdir/bar.txt "bar\n") | |
| set(${dirs_var} subdir PARENT_SCOPE) | |
| set(${added_var} zot.txt subdir/foo.txt subdir/bar.txt PARENT_SCOPE) | |
| set(${removed_var} bar.txt PARENT_SCOPE) | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to change existing files | |
| function(change_content dir) | |
| file(APPEND ${TOP}/${dir}/foo.txt "foo line 3\n") | |
| file(APPEND ${TOP}/${dir}/subdir/foo.txt "foo line 2\n") | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to create local modifications before update | |
| function(modify_content dir) | |
| file(APPEND ${TOP}/${dir}/CTestConfig.cmake "# local modification\n") | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to write CTestConfiguration.ini content. | |
| function(create_build_tree src_dir bin_dir) | |
| file(MAKE_DIRECTORY ${TOP}/${bin_dir}) | |
| file(WRITE ${TOP}/${bin_dir}/CTestConfiguration.ini | |
| "# CTest Configuration File | |
| SourceDirectory: ${TOP}/${src_dir} | |
| BuildDirectory: ${TOP}/${bin_dir} | |
| Site: test.site | |
| BuildName: user-test | |
| ") | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to write the dashboard test script. | |
| function(create_dashboard_script bin_dir custom_text) | |
| if (NOT ctest_update_check) | |
| set(ctest_update_check [[ | |
| if(ret LESS 0) | |
| message(FATAL_ERROR "ctest_update failed with ${ret}") | |
| endif() | |
| ]]) | |
| endif() | |
| # Write the dashboard script. | |
| file(WRITE ${TOP}/${bin_dir}.cmake | |
| "# CTest Dashboard Script | |
| set(CTEST_DASHBOARD_ROOT \"${TOP}\") | |
| set(CTEST_SITE test.site) | |
| set(CTEST_BUILD_NAME dash-test) | |
| set(CTEST_SOURCE_DIRECTORY \${CTEST_DASHBOARD_ROOT}/dash-source) | |
| set(CTEST_BINARY_DIRECTORY \${CTEST_DASHBOARD_ROOT}/${bin_dir}) | |
| ${custom_text} | |
| # Start a dashboard and run the update step | |
| ctest_start(Experimental) | |
| ctest_update(SOURCE \${CTEST_SOURCE_DIRECTORY} RETURN_VALUE ret ${ctest_update_args}) | |
| ${ctest_update_check}") | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to run the dashboard through the command line | |
| function(run_dashboard_command_line bin_dir) | |
| run_child( | |
| WORKING_DIRECTORY ${TOP}/${bin_dir} | |
| COMMAND ${CMAKE_CTEST_COMMAND} -M Experimental -T Start -T Update | |
| ) | |
| # Verify the updates reported by CTest. | |
| list(APPEND UPDATE_MAYBE Updated{subdir}) | |
| set(_modified Modified{CTestConfig.cmake}) | |
| if(UPDATE_NO_MODIFIED) | |
| set(_modified "") | |
| endif() | |
| check_updates(${bin_dir} | |
| Updated{foo.txt} | |
| Updated{bar.txt} | |
| Updated{zot.txt} | |
| Updated{subdir/foo.txt} | |
| Updated{subdir/bar.txt} | |
| ${_modified} | |
| ) | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to find the Update.xml file and make sure | |
| # it only has the Revision in it and no updates | |
| function(check_no_update bin_dir) | |
| set(PATTERN ${TOP}/${bin_dir}/Testing/*/Update.xml) | |
| file(GLOB UPDATE_XML_FILE RELATIVE ${TOP} ${PATTERN}) | |
| string(REGEX REPLACE "//Update.xml$" "/Update.xml" | |
| UPDATE_XML_FILE "${UPDATE_XML_FILE}") | |
| message(" found ${UPDATE_XML_FILE}") | |
| set(rev_regex "Revision|PriorRevision") | |
| file(STRINGS ${TOP}/${UPDATE_XML_FILE} UPDATE_XML_REVISIONS | |
| REGEX "^\t<(${rev_regex})>[^<\n]+</(${rev_regex})>$" | |
| ) | |
| set(found_revisions FALSE) | |
| foreach(r IN LISTS UPDATE_XML_REVISIONS) | |
| if("${r}" MATCHES "PriorRevision") | |
| message(FATAL_ERROR "Found PriorRevision in no update test") | |
| endif() | |
| if(DEFINED NO_UPDATE_REVISION AND | |
| NOT "${r}" MATCHES "<Revision>${NO_UPDATE_REVISION}</Revision>") | |
| message(FATAL_ERROR "Missing expected <Revision> in no update test") | |
| endif() | |
| if("${r}" MATCHES "<Revision>") | |
| set(found_revisions TRUE) | |
| endif() | |
| endforeach() | |
| if(found_revisions) | |
| message(" found <Revision> in no update test") | |
| else() | |
| message(FATAL_ERROR " missing <Revision> in no update test") | |
| endif() | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to find the Update.xml file and make sure | |
| # it only has the UpdateReturnStatus failure message and no updates. | |
| function(check_fail_update bin_dir) | |
| set(PATTERN ${TOP}/${bin_dir}/Testing/*/Update.xml) | |
| file(GLOB UPDATE_XML_FILE RELATIVE ${TOP} ${PATTERN}) | |
| string(REGEX REPLACE "//Update.xml$" "/Update.xml" | |
| UPDATE_XML_FILE "${UPDATE_XML_FILE}") | |
| message(" found ${UPDATE_XML_FILE}") | |
| file(STRINGS ${TOP}/${UPDATE_XML_FILE} UPDATE_XML_STATUS | |
| REGEX "^\t<UpdateReturnStatus>[^<\n]+" | |
| ) | |
| if(UPDATE_XML_STATUS MATCHES "Update command failed") | |
| message(" correctly found 'Update command failed'") | |
| else() | |
| message(FATAL_ERROR " missing 'Update command failed'") | |
| endif() | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to run the dashboard through a script | |
| function(run_dashboard_script bin_dir) | |
| run_child( | |
| WORKING_DIRECTORY ${TOP} | |
| COMMAND ${CMAKE_CTEST_COMMAND} -S ${bin_dir}.cmake -V | |
| ) | |
| # Verify the updates reported by CTest. | |
| list(APPEND UPDATE_MAYBE Updated{subdir} Updated{CTestConfig.cmake}) | |
| if(NO_UPDATE) | |
| check_no_update(${bin_dir}) | |
| elseif(FAIL_UPDATE) | |
| check_fail_update(${bin_dir}) | |
| else() | |
| check_updates(${bin_dir} | |
| Updated{foo.txt} | |
| Updated{bar.txt} | |
| Updated{zot.txt} | |
| Updated{subdir/foo.txt} | |
| Updated{subdir/bar.txt} | |
| ) | |
| endif() | |
| # Pass console output up to the parent, in case they'd like to inspect it. | |
| set(OUTPUT "${OUTPUT}" PARENT_SCOPE) | |
| endfunction() | |
| #----------------------------------------------------------------------------- | |
| # Function to initialize the testing directory. | |
| function(init_testing) | |
| file(REMOVE_RECURSE ${TOP}) | |
| file(MAKE_DIRECTORY ${TOP}) | |
| endfunction() |