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  done
205
206cat <<EOF > "${CONFIG_JSON}"
207{
208  "configuration":
209      {
210      "remove_workspace": true,
211      "include_assembly": true
212      },
213  "parameters":
214      {
215      "objdump": "${OBJDUMP}",
216      "readelf": "${READELF}",
217      "sources": $(get_scm_sources),
218      "workspace": "${1:-$WORKSPACE}",
219      "output_file": "${INTERMEDIATE_LAYER_FILE}",
220      "metadata": "${2}"
221      },
222  "elfs": [
223          ${bin_section}
224      ]
225}
226EOF
227}
228
229################################################################################
230# Appends a trace file glob to a given elf/axf file.
231#
232# GLOBALS:
233#   TRACE_FILES
234# ARGUMENTS:
235#   $1: Full path to binary (elf/axf) file.
236#   $2: Full path trace glob belonging to traces linked to the binary file.
237# OUTPUTS:
238#   Appended trace glob to the TRACE_FILES global array
239################################################################################
240append_elf_file() {
241  local elf_file="$1"
242  local trace_glob="$2"
243  TRACE_FILES[$elf_file]+="\"$trace_glob\",
244  "
245}
246
247################################################################################
248# Moves trace files from one location to another.
249#
250# Copies the trace files from one location to another hardcoded folder setup
251# where later can be processed for the intermediate layer.
252#
253# GLOBALS:
254#   COUNTER, CODE_COVERAGE_FOLDER, TRACE_FILE_PREFIX
255# ARGUMENTS:
256#   $1: Full path where the trace files reside.
257#   $2: Variable by reference that contains the new path for the trace files.
258# OUTPUTS:
259#   Path where the trace files were copied.
260################################################################################
261move_log_files() {
262  local origin_folder="$1"
263  declare -n local_trace_folder=$2
264  COUNTER=$(( COUNTER + 1))
265  local destination_trace_folder=${CODE_COVERAGE_FOLDER}/traces-${COUNTER}
266  mkdir -p ${destination_trace_folder}
267  find ${origin_folder} -maxdepth 1 -name ${TRACE_FILE_PREFIX}'-*.log' -type f -size +0 -exec cp {} ${destination_trace_folder} \;
268  # Pass the destination trace folder to calling script
269  local_trace_folder="${destination_trace_folder}/${TRACE_FILE_PREFIX:-covtrace}"'-*.log'
270}
271
272################################################################################
273# Generates intermediate layer (json) from configuration file
274# GLOBALS:
275#   CODE_COVERAGE_FOLDER, QA_REPO_NAME, CONFIG_JSON
276# ARGUMENTS:
277#   None
278# OUTPUTS:
279#   Intermediate layer (json) file at the folder indicated in the configuration
280#   file.
281################################################################################
282generate_intermediate_layer() {
283  python3 ${CODE_COVERAGE_FOLDER}/${QA_REPO_NAME}/coverage-tool/coverage-reporting/intermediate_layer.py \
284    --config-json ${CONFIG_JSON}
285}
286
287################################################################################
288# Creates LCOV coverage report.
289# GLOBALS:
290#   CODE_COVERAGE_FOLDER, workspace, INTERMEDIATE_LAYER_FILE, INFO_FILE,
291#   REPORT_FOLDER
292# ARGUMENTS:
293#   None
294# OUTPUTS:
295#   A coverage info file.
296#   LCOV HTML coverage report.
297# RETURN:
298#   0 if succeeds, non-zero on error.
299################################################################################
300create_coverage_report() {
301	python3 ${CODE_COVERAGE_FOLDER}/${QA_REPO_NAME}/coverage-tool/coverage-reporting/generate_info_file.py \
302	--workspace ${WORKSPACE} --json ${INTERMEDIATE_LAYER_FILE} --info ${INFO_FILE}
303	genhtml --branch-coverage ${INFO_FILE} --output-directory ${REPORT_FOLDER}
304}
305
306
307################################################################################
308# Creates an HTML table with the summary of the code coverage.
309#
310# Extracts the summary indicators from the main html code coverage report and
311# creates an HTML report to be shown on the building page.
312#
313# GLOBALS:
314#   REPORT_FOLDER, WORKSPACE, BUILD_URL
315# ARGUMENTS:
316#   $1: Full path where HTML report will be created.
317#   $2: Location of the main html file for the code coverage.
318# OUTPUTS:
319#   HTML report code coverage summary.
320# RETURN:
321#   0 if succeeds, non-zero on error.
322################################################################################
323generate_header() {
324    local out_report=$1
325    local cov_html=${2:-$REPORT_FOLDER/index.html}
326python3 - << EOF
327import re
328import json
329import os
330
331cov_html="$cov_html"
332out_report = "$out_report"
333origin_html = os.path.relpath(cov_html, "$WORKSPACE")
334
335with open(cov_html, "r") as f:
336    html_content = f.read()
337items = ["Lines", "Functions", "Branches"]
338s = """
339<style>
340/* Result colors */
341.success {
342background-color: #b4fd98;
343}
344.failure {
345background-color: #ffb8b8;
346}
347.unstable {
348background-color: #ffe133;
349}
350</style>
351    <div id="div-cov">
352    <hr>
353        <table id="table-cov">
354              <tbody>
355                <tr>
356                    <td>Type</td>
357                    <td>Hit</td>
358                    <td>Total</td>
359                    <td>Coverage</td>
360              </tr>
361"""
362for item in items:
363    data = re.findall(r'<td class="headerItem">{}:</td>\n\s+<td class="headerCovTableEntry">(.+?)</td>\n\s+<td class="headerCovTableEntry">(.+?)</td>\n\s+'.format(item),
364    html_content, re.DOTALL)
365    if data is None:
366        continue
367    hit, total = data[0]
368    cov = round(float(hit)/float(total) * 100.0, 2)
369    color = "success"
370    if cov < 90:
371        color = "unstable"
372    if cov < 75:
373        color = "failure"
374    s = s + """
375                <tr>
376                    <td>{}</td>
377                    <td>{}</td>
378                    <td>{}</td>
379                    <td class='{}'>{} %</td>
380                </tr>
381""".format(item, hit, total, color, cov)
382s = s + """
383            </tbody>
384        </table>
385        <p>
386        <button onclick="window.open('{}artifact/{}','_blank');">Total Coverage Report</button>
387        </p>
388    </div>
389
390""".format("$BUILD_URL", origin_html)
391with open(out_report, "a") as f:
392    f.write(s)
393EOF
394}
395