summaryrefslogtreecommitdiff
path: root/Tests/CTestUpdateCommon.cmake
blob: 77b33989a8e05e055208ad09390a9df6f86773a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#-----------------------------------------------------------------------------
# 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 ${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
    set(MSG "${MSG}Update.xml is missing expected entries:\n")
    foreach(f ${MISSING})
      set(MSG "${MSG}  ${f}\n")
    endforeach()
  else()
    # Success
    message(" no entries missing from Update.xml")
  endif()

  # Report the result
  if(EXTRA)
    # List the extra entries
    set(MSG "${MSG}Update.xml has extra unexpected entries:\n")
    foreach(f ${EXTRA})
      set(MSG "${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}")
      set(MSG "${MSG}Update log:\n  ${UPDATE_LOG}")
    else()
      set(MSG "${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_PROJECT_NAME TestProject)
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)
  # 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})
")
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_revisons FALSE)
  foreach(r IN LISTS UPDATE_XML_REVISIONS)
    if("${r}" MATCHES "PriorRevision")
      message(FATAL_ERROR "Found PriorRevision in no update test")
    endif()
    if("${r}" MATCHES "<Revision>")
      set(found_revisons TRUE)
    endif()
  endforeach()
  if(found_revisons)
    message(" found <Revision> in no update test")
  else()
    message(FATAL_ERROR " missing <Revision> in no update test")
  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})
  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()