github.com/canonical/ubuntu-image@v0.0.0-20240430122802-2202fe98b290/tests/lib/external/snapd-testing-tools/utils/log-analyzer (about) 1 #!/bin/bash 2 3 show_help() { 4 echo "usage: log-analyzer list-failed-tasks <EXEC-PARAM> <PARSED-LOG>" 5 echo " log-analyzer list-executed-tasks <EXEC-PARAM> <PARSED-LOG>" 6 echo " log-analyzer list-successful-tasks <EXEC-PARAM> <PARSED-LOG>" 7 echo " log-analyzer list-aborted-tasks <EXEC-PARAM> <PARSED-LOG>" 8 echo " log-analyzer list-all-tasks <EXEC-PARAM>" 9 echo " log-analyzer list-reexecute-tasks <EXEC-PARAM> <PARSED-LOG>" 10 echo "" 11 echo "The log analyzer is an utility wchi provides useful information about a spread" 12 echo "execution. The main functionality of the analyzer utility is to determine which tests" 13 echo "have to be re-executed, being able to include the tests that have been aborted, even" 14 echo "when those tests are not included in the test results." 15 echo "The log analyzer uses as input the spread expression that was used to run the tests," 16 echo "this expression determines which are all the tests to be considered. The second input" 17 echo "is the output of the log-parser utility, which generates a json file including all the" 18 echo "information extracted from the raw spread log" 19 echo "" 20 echo "Available options:" 21 echo " -h --help show this help message." 22 echo "" 23 echo "COMMANDS:" 24 echo " list-failed-tasks list the tasks that failed during execute" 25 echo " list-executed-tasks list the tasks that were executed" 26 echo " list-successful-tasks list the successful tasks" 27 echo " list-aborted-tasks list the aborted tasks (needs spread to be installed)" 28 echo " list-all-tasks list all the tasks" 29 echo " list-reexecute-tasks list the tasks to re-execute to complete (includes aborted and failed tasks)" 30 echo "" 31 echo "PARSED-LOG: This is the output generated by the log-parser tool" 32 echo "EXEC-PARAM: this is the parameter used to run spread (something like this BACKEND:SYSTEM:SUITE)" 33 echo "" 34 } 35 36 _check_log() { 37 local log="$1" 38 39 if [ -z "$log" ]; then 40 echo "log.analyzer: the log file cannot be empty" 41 exit 1 42 elif [ ! -f "$log" ]; then 43 echo "log.analyzer: the log file $log does not exist" 44 exit 1 45 fi 46 } 47 48 _list_failed() { 49 local level="$1" 50 local stage="$2" 51 local log="$3" 52 53 if [ -z "$level" ]; then 54 echo "log.analyzer: the first parameter cannot be empty" 55 exit 1 56 elif [ ! "$level" = 'task' ] && [ ! "$level" = 'suite' ] && [ ! "$level" = 'project' ]; then 57 echo "log.analyzer: the first parameter has to be: task, suite or project" 58 exit 1 59 fi 60 61 if [ -z "$stage" ]; then 62 echo "log.analyzer: the second parameter cannot be empty" 63 exit 1 64 elif [ ! "$stage" = 'prepare' ] && [ ! "$stage" = 'restore' ]; then 65 echo "log.analyzer: the second parameter has to be: prepare or restore" 66 exit 1 67 fi 68 _check_log "$log" 69 70 jq -r ".[] | select( .type == \"result\") | select( .result_type == \"Failed\") | select(.level == \"$level\") | select(.stage == \"$stage\") | .detail.lines[]" "$log" | cut -d '-' -f2- | xargs 71 } 72 73 _merge_tasks_lists() { 74 # Returns the list1 + the tasks in list2 which are not included in list1 75 local list1="$1" 76 local list2="$2" 77 local merged_list="$1" 78 local list1_file 79 80 list1_file=$(mktemp list1.XXXXXX) 81 for elem in $list1; do 82 echo "$elem" >> "$list1_file" 83 done 84 85 # shellcheck disable=SC2086 86 for elem2 in $list2; do 87 if ! grep -Fxq "$elem2" "$list1_file"; then 88 merged_list="$merged_list $elem2" 89 fi 90 done 91 rm "$list1_file" 92 echo "$merged_list" 93 } 94 95 _diff_tasks_lists() { 96 # Returns the list1 - the tasks in list2 97 local list1="$1" 98 local list2="$2" 99 local diff_list list2_file 100 101 diff_list="" 102 list2_file=$(mktemp list2.XXXXXX) 103 for elem in $list2; do 104 echo "$elem" >> "$list2_file" 105 done 106 107 # shellcheck disable=SC2086 108 for elem1 in $list1; do 109 if ! grep -Fxq "$elem1" "$list2_file"; then 110 diff_list="$diff_list $elem1" 111 fi 112 done 113 rm "$list2_file" 114 echo "$diff_list" 115 } 116 117 _intersection_tasks_lists() { 118 # Returns the tasks in list1 which are also in the list2 119 local list1="$1" 120 local list2="$2" 121 local both_list list2_file 122 123 both_list="" 124 list2_file=$(mktemp list2.XXXXXX) 125 for elem in $list2; do 126 echo "$elem" >> "$list2_file" 127 done 128 for elem in $list1; do 129 # -F tells grep to look for fixed strings, not regexps 130 if grep -Fxq "$elem" "$list2_file"; then 131 both_list="$both_list $elem" 132 fi 133 done 134 rm "$list2_file" 135 echo "$both_list" 136 } 137 138 list_all_tasks() { 139 local exec_exp="$1" 140 exec_exp="$(echo "$exec_exp" | tr ',' ' ')" 141 if ! command -v spread >/dev/null; then 142 echo "log.analyzer: spread tool is not installed, exiting..." 143 exit 1 144 fi 145 146 # shellcheck disable=SC2086 147 spread -list $exec_exp 148 } 149 150 _list_executed_and_failed_tasks() { 151 local exec_exp="$1" 152 local log="$2" 153 154 if ! command -v spread >/dev/null; then 155 echo "log.analyzer: spread tool is not installed, exiting..." 156 exit 1 157 fi 158 _check_log "$log" 159 160 local failed_tasks failed_tasks_restore failed_tasks_prepare exec_and_failed_tasks 161 failed_tasks="$(list_failed_tasks "$exec_exp" "$log")" 162 failed_tasks_prepare="$(_list_failed task prepare "$log")" 163 failed_tasks_restore="$(_list_failed task restore "$log")" 164 165 exec_and_failed_tasks="$(_merge_tasks_lists "$failed_tasks" "$failed_tasks_restore")" 166 _diff_tasks_lists "$exec_and_failed_tasks" "$failed_tasks_prepare" 167 } 168 169 list_failed_tasks() { 170 local exec_exp="$1" 171 local log="$2" 172 173 if [ -z "$exec_exp" ]; then 174 echo "log.analyzer: execution expression for spread cannot be empty" 175 exit 1 176 fi 177 exec_exp="$(echo "$exec_exp" | tr ',' ' ')" 178 _check_log "$log" 179 180 local all_tasks failed_tasks 181 all_tasks="$(list_all_tasks "$exec_exp")" 182 failed_tasks="$(jq -r '.[] | select( .type == "result") | select( .result_type == "Failed") | select(.level == "tasks") | .detail.lines[]' "$log" | cut -d '-' -f2- | xargs)" 183 _intersection_tasks_lists "$failed_tasks" "$all_tasks" 184 } 185 186 list_reexecute_tasks() { 187 local exec_exp="$1" 188 local log="$2" 189 190 if [ -z "$exec_exp" ]; then 191 echo "log.analyzer: execution expression for spread cannot be empty" 192 exit 1 193 fi 194 exec_exp="$(echo "$exec_exp" | tr ',' ' ')" 195 _check_log "$log" 196 197 local aborted_tasks exec_and_failed_tasks all_tasks reexec_tasks 198 aborted_tasks="$(list_aborted_tasks "$exec_exp" "$log")" 199 all_tasks="$(list_all_tasks "$exec_exp")" 200 exec_and_failed_tasks="$(_list_executed_and_failed_tasks "$exec_exp" "$log")" 201 202 # Remove the tasks which are not in the filter from the executed and failed 203 exec_and_failed_tasks="$(_intersection_tasks_lists "$exec_and_failed_tasks" "$all_tasks")" 204 reexec_tasks="$(_merge_tasks_lists "$aborted_tasks" "$exec_and_failed_tasks")" 205 206 # In case all the tests are failed or aborted, then the execution expression is used to reexecute 207 if [ "$(echo "$reexec_tasks" | wc -w)" = "$(echo "$all_tasks" | wc -w)" ]; then 208 echo "$exec_exp" 209 return 210 fi 211 212 # When all the tests were successful, then no tests need to be reexecuted 213 if [ "$(echo "$reexec_tasks" | wc -w)" = 0 ]; then 214 return 215 fi 216 echo "$reexec_tasks" 217 } 218 219 list_successful_tasks() { 220 local exec_exp="$1" 221 local log="$2" 222 223 if [ -z "$exec_exp" ]; then 224 echo "log.analyzer: execution expression for spread cannot be empty" 225 exit 1 226 fi 227 exec_exp="$(echo "$exec_exp" | tr ',' ' ')" 228 _check_log "$log" 229 230 local all_tasks executed_tasks failed_tasks_restore failed_tasks 231 all_tasks="$(list_all_tasks "$exec_exp")" 232 executed_tasks="$(list_executed_tasks "$exec_exp" "$log")" 233 executed_tasks="$(_intersection_tasks_lists "$executed_tasks" "$all_tasks")" 234 failed_tasks="$(list_failed_tasks "$exec_exp" "$log")" 235 failed_tasks_restore="$(_list_failed task restore "$log")" 236 237 if [ -n "$failed_tasks_restore" ]; then 238 failed_tasks="$(_merge_tasks_lists "$failed_tasks" "$failed_tasks_restore")" 239 fi 240 241 if [ -n "$failed_tasks" ]; then 242 executed_tasks="$(_diff_tasks_lists "$executed_tasks" "$failed_tasks")" 243 fi 244 245 echo "$executed_tasks" 246 } 247 248 list_executed_tasks() { 249 local exec_exp="$1" 250 local log="$2" 251 252 if [ -z "$exec_exp" ]; then 253 echo "log.analyzer: execution expression for spread cannot be empty" 254 exit 1 255 fi 256 exec_exp="$(echo "$exec_exp" | tr ',' ' ')" 257 _check_log "$log" 258 259 local all_tasks executed_tasks 260 all_tasks="$(list_all_tasks "$exec_exp")" 261 executed_tasks="$(jq -r '.[] | select( .type == "action") | select( .verb == "Executing") | .task' "$log")" 262 _intersection_tasks_lists "$executed_tasks" "$all_tasks" 263 } 264 265 list_aborted_tasks() { 266 local exec_exp="$1" 267 local log="$2" 268 269 if [ -z "$exec_exp" ]; then 270 echo "log.analyzer: execution expression for spread cannot be empty" 271 exit 1 272 fi 273 exec_exp="$(echo "$exec_exp" | tr ',' ' ')" 274 _check_log "$log" 275 276 local all_tasks executed_tasks failed_tasks_prepare failed_tasks_restore failed_tasks 277 all_tasks="$(list_all_tasks "$exec_exp")" 278 executed_tasks="$(list_executed_tasks "$exec_exp" "$log")" 279 failed_tasks="$(list_failed_tasks "$exec_exp" "$log")" 280 failed_tasks_prepare="$(_list_failed task prepare "$log")" 281 failed_tasks_restore="$(_list_failed task restore "$log")" 282 283 # In case no tasks for the expression, the aborted list is empty 284 if [ -z "$all_tasks" ]; then 285 return 286 fi 287 288 # In case no tasks are successfully executed, all the tasks - the failed ones are the aborted 289 if [ -z "$executed_tasks" ]; then 290 exec_and_failed_tasks="$(_list_executed_and_failed_tasks "$exec_exp" "$log")" 291 _diff_tasks_lists "$all_tasks" "$exec_and_failed_tasks" 292 return 293 fi 294 295 # In other cases the aborted tasks are all the tasks - the executed - the that failed 296 _diff_tasks_lists "$all_tasks" "$executed_tasks" 297 } 298 299 main() { 300 if [ $# -eq 0 ]; then 301 show_help 302 exit 0 303 fi 304 305 local subcommand="$1" 306 local action= 307 while [ $# -gt 0 ]; do 308 case "$1" in 309 -h|--help) 310 show_help 311 exit 0 312 ;; 313 *) 314 action=$(echo "$subcommand" | tr '-' '_') 315 shift 316 break 317 ;; 318 esac 319 done 320 321 if [ -z "$(declare -f "$action")" ]; then 322 echo "log.analyzer: no such command: $subcommand" >&2 323 show_help 324 exit 1 325 fi 326 327 "$action" "$@" 328 } 329 330 main "$@"