1#!/bin/bash 2# 3# Copyright 2023 The Hafnium Authors. 4# 5# Use of this source code is governed by a BSD-style 6# license that can be found in the LICENSE file or at 7# https://opensource.org/licenses/BSD-3-Clause. 8 9################################################################################ 10# CI VARIABLES: 11# workspace, warehouse, artefacts 12# GLOBAL VARIABLES: 13# OUTDIR, PROJECT, FALLBACK_PLUGIN_URL, FALLBACK_FILES, PLUGIN_BINARY 14################################################################################ 15set +u 16QA_REPO=${QA_REPO_PUBLIC:-https://git.gitlab.arm.com/tooling/qa-tools.git} 17QA_REPO_NAME=qa-tools 18QA_REFSPEC=${QA_REFSPEC:-master} 19# Internal globals 20DEBUG_FOLDER=${artefacts}/debug 21RELEASE_FOLDER=${artefacts}/release 22TRACE_FILE_PREFIX=covtrace 23PROJECT="HAFNIUM" 24BIN_SECTION="" 25# INDEXED BY ELF FILE 26declare -g -A TRACE_FILES=() 27declare -g COUNTER=0 28 29 30################################################################################ 31# Enable the code coverage tool 32# 33# This function enables the plugin to produce trace logs on the FVP 34# and set variables for the post-processing stage. 35# 36# GLOBALS: 37# WORKSPACE, CODE_COVERAGE_FOLDER, INFO_FILE, REPORT_FOLDER, CONFIG_JSON, 38# INTERMEDIATE_LAYER_FILE, OBJDUMP, READELF, FALLBACK_FILES 39# ARGUMENTS: 40# None 41# OUTPUTS: 42# lcov binaries 43# RETURN: 44# 0 if succeeds, non-zero on error. 45################################################################################ 46enable_code_coverage() { 47 # Load code coverage binary 48 echo "Code coverage for binaries enabled..." 49 export OUTDIR=${WORKSPACE}/out/reference 50 mkdir -p $OUTDIR 51 CODE_COVERAGE_FOLDER="${OUTDIR}/qa-code-coverage" 52 INFO_FILE=${CODE_COVERAGE_FOLDER}/coverage.info 53 REPORT_FOLDER=${CODE_COVERAGE_FOLDER}/lcov 54 CONFIG_JSON=${CODE_COVERAGE_FOLDER}/configuration_file.json 55 INTERMEDIATE_LAYER_FILE=${CODE_COVERAGE_FOLDER}/intermediate_layer.json 56 OBJDUMP="$(which 'aarch64-none-elf-objdump')" 57 READELF="$(which 'aarch64-none-elf-readelf')" 58 FALLBACK_FILES="coverage_trace.so,coverage_trace.o,plugin_utils.o" 59 build_tool 60 lcov --version || install_lcov 61} 62 63 64################################################################################ 65# Install lcov from source 66# 67# GLOBALS: 68# None 69# ARGUMENTS: 70# $1 Folder where lcov will be installed 71# $2 Lcov version to be installed 72# OUTPUTS: 73# lcov binaries 74# RETURN: 75# 0 if succeeds, non-zero on error. 76################################################################################ 77install_lcov() { 78 local lcov_folder=${1:-$HOME/lcov} 79 local lcov_version=${2:-v1.16} 80 81 echo "Cloning lcov ${lcov_version} at folder $(pwd)..." 82 git clone https://github.com/linux-test-project/lcov.git 83 cd lcov 84 git checkout $lcov_version 85 echo "Installing lcov at folder ${lcov_folder}..." 86 make PREFIX=${lcov_folder} install 87 cd .. 88 # Make it available 89 export PATH=$PATH:${lcov_folder}/bin 90 lcov --version 91 genhtml --version 92} 93 94 95################################################################################ 96# Deploy qa-tools into the current directory 97# GLOBALS: 98# QA_REPO, QA_REPO_NAME, QA_REFSPEC 99# ARGUMENTS: 100# None 101# OUTPUTS: 102# Clones the qa-tools repo from the global variables with the given 103# commit hash. 104# RETURN: 105# 0 if succeeds, non-zero on error. 106################################################################################ 107deploy_qa_tools() { 108 git clone "${QA_REPO}" ${QA_REPO_NAME} 109 cd ${QA_REPO_NAME} && git checkout "${QA_REFSPEC}" && cd .. 110} 111 112 113################################################################################ 114# Builds or downloads the QA Code Coverage Tool 115# GLOBALS: 116# CODE_COVERAGE_FOLDER, QA_REPO, QA_REPO_NAME, QA_REFSPEC, FALLBACK_PLUGIN_URL 117# ARGUMENTS: 118# None 119# OUTPUTS: 120# Creates coverage folder and builds/downloads there the plugin binaries. 121# It exports the binary plugin location to coverage_trace_plugin. 122# RETURN: 123# 0 if succeeds, non-zero on error. 124################################################################################ 125build_tool() { 126 echo "Building QA Code coverage tool..." 127 PLUGIN_BINARY="${FALLBACK_FILES%%,*}" # The first in the list of the binary files 128 local PVLIB_HOME="warehouse/SysGen/PVModelLib/$model_version/$model_build/external" 129 local LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CODE_COVERAGE_FOLDER 130 mkdir -p ${CODE_COVERAGE_FOLDER} 131 pushd "${CODE_COVERAGE_FOLDER}" 132 deploy_qa_tools 133 local cc_source=$(find . -type f -name 'coverage_trace.cc') 134 for ff in ${FALLBACK_FILES//,/ } 135 do 136 wget -q "${FALLBACK_PLUGIN_URL}/${ff}" 137 done 138 export coverage_trace_plugin="${CODE_COVERAGE_FOLDER}/${PLUGIN_BINARY}" 139 popd 140} 141 142################################################################################ 143# Returns the sources (SCM) indicated in the configuration file. 144# 145# Returns a multiline string in JSON format that contains the sources used to 146# build the binaries for the defined project. 147# 148# ENVIRONMENT VARIABLES: 149# GERRIT_PROJECT, GERRIT_REFSPEC 150# ARGUMENTS: 151# OUTPUTS: 152# Source SCM parameters in a JSON format as string. 153# RETURN: 154# 0 if succeeds, non-zero on error. 155################################################################################ 156get_scm_sources() { 157 local scm_sources="" 158 159 read -r -d '' scm_sources << EOM 160 [ 161 { 162 "type": "git", 163 "URL": "https://review.trustedfirmware.org/hafnium/hafnium", 164 "COMMIT": "", 165 "REFSPEC": "${HF_REFSPEC}", 166 "LOCATION": "hafnium" 167 } 168 ] 169EOM 170 echo "$scm_sources" 171} 172 173################################################################################ 174# Creates the input configuration file to create the intermediate layer. 175# 176# GLOBALS: 177# TRACE_FILES, CONFIG_JSON, OBJDUMP, READELF, INTERMEDIATE_LAYER_FILE 178# ARGUMENTS: 179# $1: Workspace where the sources were located when the binaries were built. 180# $2: Optional metadata. 181# OUTPUTS: 182# Input configuration file for the intermediate layer. 183################################################################################ 184create_configuration_file() { 185 186 # Obtaining binaries from array 187 bin_section="" 188 comma="" 189 for elf_file in "${!TRACE_FILES[@]}" 190 do 191 local trace_files="${TRACE_FILES[$elf_file]}" 192 bin_section=$(cat <<-END 193 ${bin_section}${comma} 194 { 195 "name": "$elf_file", 196 "traces": [ 197 ${trace_files%,*} 198 ] 199 } 200END 201 202) 203 comma="," 204 parent="$(basename "$(dirname "$elf_file")")" 205 mkdir -p ${CODE_COVERAGE_FOLDER}/${parent} 206 cp $elf_file ${CODE_COVERAGE_FOLDER}/${parent}/. 207 done 208 209cat <<EOF > "${CONFIG_JSON}" 210{ 211 "configuration": 212 { 213 "remove_workspace": true, 214 "include_assembly": true 215 }, 216 "parameters": 217 { 218 "objdump": "${OBJDUMP}", 219 "readelf": "${READELF}", 220 "sources": $(get_scm_sources), 221 "workspace": "${1:-$WORKSPACE}", 222 "output_file": "${INTERMEDIATE_LAYER_FILE}", 223 "include_only": "${INCLUDE_FILES}", 224 "exclude": "${EXCLUDE_FILES}", 225 "metadata": "${2}" 226 }, 227 "elfs": [ 228 ${bin_section} 229 ] 230} 231EOF 232} 233 234################################################################################ 235# Appends a trace file glob to a given elf/axf file. 236# 237# GLOBALS: 238# TRACE_FILES 239# ARGUMENTS: 240# $1: Full path to binary (elf/axf) file. 241# $2: Full path trace glob belonging to traces linked to the binary file. 242# OUTPUTS: 243# Appended trace glob to the TRACE_FILES global array 244################################################################################ 245append_elf_file() { 246 local elf_file="$1" 247 local trace_glob="$2" 248 TRACE_FILES[$elf_file]+="\"$trace_glob\", 249 " 250} 251 252################################################################################ 253# Moves trace files from one location to another. 254# 255# Copies the trace files from one location to another hardcoded folder setup 256# where later can be processed for the intermediate layer. 257# 258# GLOBALS: 259# COUNTER, CODE_COVERAGE_FOLDER, TRACE_FILE_PREFIX 260# ARGUMENTS: 261# $1: Full path where the trace files reside. 262# $2: Variable by reference that contains the new path for the trace files. 263# OUTPUTS: 264# Path where the trace files were copied. 265################################################################################ 266move_log_files() { 267 local origin_folder="$1" 268 declare -n local_trace_folder=$2 269 COUNTER=$(( COUNTER + 1)) 270 local destination_trace_folder=${CODE_COVERAGE_FOLDER}/traces-${COUNTER} 271 mkdir -p ${destination_trace_folder} 272 find ${origin_folder} -maxdepth 1 -name ${TRACE_FILE_PREFIX}'-*.log' -type f -size +0 -exec cp {} ${destination_trace_folder} \; 273 # Pass the destination trace folder to calling script 274 local_trace_folder="${destination_trace_folder}/${TRACE_FILE_PREFIX:-covtrace}"'-*.log' 275} 276 277################################################################################ 278# Generates intermediate layer (json) from configuration file 279# GLOBALS: 280# CODE_COVERAGE_FOLDER, QA_REPO_NAME, CONFIG_JSON 281# ARGUMENTS: 282# None 283# OUTPUTS: 284# Intermediate layer (json) file at the folder indicated in the configuration 285# file. 286################################################################################ 287generate_intermediate_layer() { 288 python3 ${CODE_COVERAGE_FOLDER}/${QA_REPO_NAME}/coverage-tool/coverage-reporting/intermediate_layer.py \ 289 --config-json ${CONFIG_JSON} 290} 291 292################################################################################ 293# Creates LCOV coverage report. 294# GLOBALS: 295# CODE_COVERAGE_FOLDER, workspace, INTERMEDIATE_LAYER_FILE, INFO_FILE, 296# REPORT_FOLDER 297# ARGUMENTS: 298# None 299# OUTPUTS: 300# A coverage info file. 301# LCOV HTML coverage report. 302# RETURN: 303# 0 if succeeds, non-zero on error. 304################################################################################ 305create_coverage_report() { 306 python3 ${CODE_COVERAGE_FOLDER}/${QA_REPO_NAME}/coverage-tool/coverage-reporting/generate_info_file.py \ 307 --workspace ${WORKSPACE} --json ${INTERMEDIATE_LAYER_FILE} --info ${INFO_FILE} 308 genhtml --branch-coverage ${INFO_FILE} --output-directory ${REPORT_FOLDER} 309} 310 311 312################################################################################ 313# Creates an HTML table with the summary of the code coverage. 314# 315# Extracts the summary indicators from the main html code coverage report and 316# creates an HTML report to be shown on the building page. 317# 318# GLOBALS: 319# REPORT_FOLDER, WORKSPACE, BUILD_URL 320# ARGUMENTS: 321# $1: Full path where HTML report will be created. 322# $2: Location of the main html file for the code coverage. 323# OUTPUTS: 324# HTML report code coverage summary. 325# RETURN: 326# 0 if succeeds, non-zero on error. 327################################################################################ 328generate_header() { 329 local out_report=$1 330 local cov_html=${2:-$REPORT_FOLDER/index.html} 331python3 - << EOF 332import re 333import json 334import os 335 336cov_html="$cov_html" 337out_report = "$out_report" 338origin_html = os.path.relpath(cov_html, "$WORKSPACE") 339 340with open(cov_html, "r") as f: 341 html_content = f.read() 342items = ["Lines", "Functions", "Branches"] 343s = """ 344<style> 345/* Result colors */ 346.success { 347background-color: #b4fd98; 348} 349.failure { 350background-color: #ffb8b8; 351} 352.unstable { 353background-color: #ffe133; 354} 355</style> 356 <div id="div-cov"> 357 <hr> 358 <table id="table-cov"> 359 <tbody> 360 <tr> 361 <td>Type</td> 362 <td>Hit</td> 363 <td>Total</td> 364 <td>Coverage</td> 365 </tr> 366""" 367for item in items: 368 data = re.findall(r'<td class="headerItem">{}:</td>\n\s+<td class="headerCovTableEntry">(.+?)</td>\n\s+<td class="headerCovTableEntry">(.+?)</td>\n\s+'.format(item), 369 html_content, re.DOTALL) 370 if data is None: 371 continue 372 hit, total = data[0] 373 cov = round(float(hit)/float(total) * 100.0, 2) 374 color = "success" 375 if cov < 90: 376 color = "unstable" 377 if cov < 75: 378 color = "failure" 379 s = s + """ 380 <tr> 381 <td>{}</td> 382 <td>{}</td> 383 <td>{}</td> 384 <td class='{}'>{} %</td> 385 </tr> 386""".format(item, hit, total, color, cov) 387s = s + """ 388 </tbody> 389 </table> 390 <p> 391 <button onclick="window.open('{}artifact/{}','_blank');">Total Coverage Report</button> 392 </p> 393 </div> 394 395""".format("$BUILD_URL", origin_html) 396with open(out_report, "a") as f: 397 f.write(s) 398EOF 399} 400