#!/bin/bash
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# A helper for running gcov on all of the project sources.
# Usage:
#   gcov-all.sh (--prepare | --summarize) path
#
#   where path is the same location where one runs make
#
# There are two modes:
#   --prepare cleans up all the .gcda files. This should be done
#   before running the test, as we need an accurate set of these
#   to know which object files to include in the executable.
#   It also effectively zeroes all the measurements, preventing
#   different runs from getting added together.
#
#   --summarize goes through the produced files, runs gcov on
#   them, producing the gcov-summary.html and the gcov/ directory
#   with individual dumps
#
# Glossary:
#   .gcno file: produced by gcc during compilation, along with the
#               corresponding .o file
#   .gcda file: produced when an instrumented application is run
#               (or .so is loaded), and then at its exit. Contains
#               the actual measurements.
#
# To invoke gcov, we need to pass it in a list of all the source
# files we want coverage information for, as well as the directory
# to look into for the corresponding .gcno/.gcda files. The
# summarize mode collects these based on the .gcda files that exist.
#
# TODO(morlovich): evaluate lcov as an option? Its output looks nice.

function summarize {
  WORKDIR=`mktemp -d`
  SRCDIR=`pwd`
  OUTNAME=gcov-summary.html

  echo "Collecting all object and profile data into:" $WORKDIR

  # Here, we look for the .gcda files, and the .o and .gcno that go with them.
  # This is because they get generated for any object files that gets linked in,
  # as soon as the executable/module are initialized, giving us an accurate
  # picture of what should be checked

  GCDAS=`find ./out/Debug_Coverage -name '*.gcda'`
  DATA=
  for F in $GCDAS
  do
    BASE=${F%.gcda}
    GCNO=$BASE.gcno
    O=$BASE.o
    if [ ! -f $GCNO  ]; then
      echo "WARNING: can't find " $GCNO
      continue
    fi

    if [ ! -f $O  ]; then
      echo "WARNING: can't find " $O
      continue
    fi

    DATA+=" $F $GCNO $O"
  done

  cp $DATA $WORKDIR/

  echo "Generating gcov summary into file://"$PWD/$OUTNAME

  # Collect relevant sources. For each one, we check if we have the
  # gcda (which means we have gcno, too). We want this for two reasons:
  #
  # 1) We only want coverage for a file if the gcda is there
  # 2) gcov has a bug that screws up output if some files' .gcno
  # does not exist (see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=35568)
  #
  # TODO(morlovich): worry about duplicate names!
  SOURCES=`find -L $SRCDIR/net $SRCDIR/pagespeed -name '*.cc' -or -name '*.c'`

  FILTERED_SOURCES=
  for F in $SOURCES
  do
    GCDA=`basename $F .c`
    GCDA=$WORKDIR/`basename $GCDA .cc`.gcda
    if [ -f $GCDA ]; then
      if [ $F == ${F/.svn/marker/} ]; then
         FILTERED_SOURCES="$FILTERED_SOURCES $GCDA"
      fi
    fi
  done
  htmlDriver > $OUTNAME
  echo "<pre id='data' style='display:none'>" >> $OUTNAME
  gcov -o $WORKDIR $FILTERED_SOURCES >> $OUTNAME
  echo "</pre>" >> $OUTNAME

  echo "Moving all the .gcov files to gcov subdir (after wiping it)"
  rm -rf $SRCDIR/gcov
  mkdir $SRCDIR/gcov
  mv *.gcov $SRCDIR/gcov

  echo "Cleaning up..."
  rm -r $WORKDIR
}

# This outputs the html driver that visualizes the results
function htmlDriver {
  cat <<TEMPLATE_END
<!DOCTYPE html>
<head>
<script>
// Computes a color for given goodness percentage. (Using CSS3 hsl syntax)
function percentColor(percent) {
  var hue = (percent / 100 * 120).toFixed(0);
  return 'hsl(' + hue + ', 100%, 50%)';
}

// Adds a row with given DOM for the file info and given coverage
// percentage to the provided table section, giving it the appropriate color
function addResultRow(tsection, fileInfo, percent) {
  var row = tsection.insertRow(-1);
  row.style.backgroundColor = percentColor(percent);

  var fileNameCell = row.insertCell(-1);
  fileNameCell.appendChild(fileInfo);

  var percentCell = row.insertCell(-1);
  percentCell.align = 'right';
  percentCell.appendChild(document.createTextNode(percent.toFixed(2) + '%'));
}

// Adds a result for given filename and coverage percentage to the first body
// of the table with id 'outTable'
function addFileResultRow(fileName, percent) {
  var table = document.getElementById('outTable');
  var tbody = table.tBodies[0];

  // We want a link to the .gcov file here
  var a = document.createElement('a');
  a.appendChild(document.createTextNode(fileName));

  var fragments = fileName.split('/');
  a.setAttribute('href', 'gcov/' + fragments[fragments.length - 1] + '.gcov');

  addResultRow(tbody, a, percent);
}

function addSummaryResultRow(summary, percent) {
  var table = document.getElementById('outTable');
  var tfoot = table.tFoot;

  addResultRow(tfoot, document.createTextNode(summary), percent);
}

function prettifySummary() {
  // Get the raw data from the <pre id='data'>
  var preNode = document.getElementById('data');
  var txt = (preNode.textContent ? preNode.textContent : preNode.innerText);
  var allLines = txt.split('\n');

  var currentFile;

  // Collect file names, percentages, and lines
  var allFiles = []; // array of name, coverage %, lines pairs
  for (var i = 0; i < allLines.length; ++i) {
    var line = allLines[i];
    var fileInfo = /File '(.*)'/.exec(line);
    if (fileInfo) {
      currentFile = fileInfo[1];
      // get rid of ./ if needed.
      if (currentFile.substring(0, 2) == './') {
        currentFile = currentFile.substring(2);
      }
    }

    var linesInfo = /Lines executed:(.*)% of (\d+)/.exec(line);
    if (linesInfo) {
      allFiles.push([currentFile, Number(linesInfo[1]), Number(linesInfo[2])]);
    }

    if (/No executable lines/.exec(line)) {
      allFiles.push([currentFile, 0, 0]);
    }
  }

  // Sort by filename
  allFiles.sort(function(a, b) {
    if (a[0] < b[0]) {
      return -1;
    } else if (a[0] == b[0]) {
      return 0;
    } else {
      return 1;
    }
  });

  // Append all results we want to table, coloring by coverage; and also compute
  // an overall number (which may include a few things we don't care about)
  var totalLines   = 0;
  var totalCovered = 0;
  for (var i = 0; i < allFiles.length; ++i) {
    var fileName = allFiles[i][0];
    var percent = allFiles[i][1];
    var lines = allFiles[i][2];

    // Skip paths -- we don't need coverage information for system headers
    if (fileName.charAt(0) == '/') {
      continue;
    }

    totalLines += lines;
    totalCovered += Math.round(lines * percent / 100);

    addFileResultRow(fileName, percent);
  }

  addSummaryResultRow('Total (' + totalCovered + '/' + totalLines +')',
                      totalCovered / totalLines * 100);
}
</script>
</head>
<body onload="prettifySummary()">
  <table id="outTable">
    <thead>
      <tr><th>File name</th><th>Coverage percentage</th></tr>
    </thead>
    <tbody></tbody>
    <tfoot style="font-weight:bold; "></tfoot>
  </table>
TEMPLATE_END
}

function usage {
  echo "Usage:" $0 "(--prepare | --summarize) path"
}

if [ -z $2 ]; then
  usage
  exit
fi

cd $2

case $1 in
--prepare)
  echo "Removing old .gcda files"
  find $2/out/Debug_Coverage -name '*.gcda' -delete;;
--summarize)
  summarize;;
*)
  usage;;
esac

