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