classdef BullseyeAnnotations < coder.coverage.Annotations
%Code coverage annotations for the HTML code generation report
%
% See also
%
% This is an undocumented class. Its methods and properties are likely to
% change without warning from one release to the next.

% Copyright 2009-2016 The MathWorks, Inc.

properties(Access=private)
Kinds;
Events;
Columns;
Lines;
FnCov;
FnTotal;
CdCov;
CdTotal;
ComponentCovData;
CodeGenFolder;
ComponentObjDir;
ComponentDir;
ComponentObjDirs;
end

methods (Access=private)

function result = i_filename_cmp(~, srcPathFromCovFile, srcPathFromBuildInfo)
if strncmpi(matlabroot, srcPathFromBuildInfo, length(matlabroot))
% If srcPathFromBuildInfo is underneath matlabroot, the criterion for a match is
% less strict: we only require the part of the path below
% matlabroot to be an exact match. This is required to handle
% the case where matlabroot has spaces and the path in the .cov
% file has been transformed to use a subst'd drive letter
% without spaces.
N = length(srcPathFromBuildInfo)-length(matlabroot)+2;
result = strncmpi(srcPathFromCovFile(end:-1:1), srcPathFromBuildInfo(end:-1:1), N);
else
result = strcmpi(srcPathFromCovFile, srcPathFromBuildInfo);
end
end
end

methods (Access=public)

function summary = getSummaryHtml(this)

fn_cov = this.FnCov;
fn_total = this.FnTotal;
cd_cov = this.CdCov;
cd_total = this.CdTotal;

[fnLabel, fnBar] = coder.coverage.Annotations.getHtmlSummaryBar...
(fn_cov,fn_total,'CoderCoverage:LDRA:FunctionCoverageLabel',...
'CoderCoverage:LDRA:FunctionCoverageTooltip');

[cdLabel, cdBar] = coder.coverage.Annotations.getHtmlSummaryBar...
(cd_cov,cd_total, 'CoderCoverage:Bullseye:BullseyeConditionDecisionLabel',...
'CoderCoverage:Bullseye:BullseyeConditionDecisionTooltip');

summary = sprintf('%s\n<p>%s %s %s %s\n<p>',...
DAStudio.message('CoderCoverage:Bullseye:CoverageSummary'), ...
fnLabel, fnBar, cdLabel, cdBar);

end

function [lineNos, colNos, textStr1, textStr1Status, textStr2, textStr2Status,...
toolTips] = getAnnotations(this)
lineNos = this.Lines;
colNos = this.Columns;
events = this.Events;
kinds = this.Kinds;

textStr1 = cell(size(lineNos));
textStr1Status = cell(size(lineNos));
textStr2 = cell(size(lineNos));
textStr2Status = cell(size(lineNos));
toolTips = cell(size(lineNos));

for i=1:length(kinds)
str2=''; stat2='';
kind = kinds{i};
event = events{i};
switch kind
case {'decision'}
switch event
case 'none'
tip='Decision not covered';
str1='&nbsp;&nbsp;=&gt;';
stat1='F';
case 'full'
tip='Decision covered true and false';
str1='TF&nbsp;&nbsp;';
stat1='T';
case 'true'
tip='Decision covered true, but not false';
str1='&nbsp;=&gt;';
stat1='F';
str2='T';
stat2='T';
case 'false'
tip='Decision covered false, but not true';
str1='&nbsp;=&gt;';
stat1='F';
str2='F';
stat2='T';
end
case {'function'}
switch event
case 'none'
tip='Function not called';
str1='=&gt;&nbsp;&nbsp;';
stat1='F';
case 'full'
tip='Function called';
str1='Fcn&nbsp;';
stat1='T';
end
case {'switch-label'}
switch event
case 'none'
tip='Switch label not covered';
str1='=&gt;&nbsp;&nbsp;';
stat1='F';
case 'full'
tip='Switch label covered';
str1='Sw&nbsp;&nbsp;';
stat1='T';
end
case {'try'}
switch event
case 'none'
tip='Try block never completed';
str1='=&gt;&nbsp;&nbsp;';
stat1='F';
case 'full'
tip='Try block covered';
str1='Try&nbsp;';
stat1='T';
end
case {'catch'}
switch event
case 'none'
tip='Catch block not covered';
str1='=&gt;&nbsp;&nbsp;';
stat1='F';
case 'full'
tip='Catch block covered';
str1='Cat&nbsp;';
stat1='T';
end
case {'constant'}
assert(strcmp(event,'none'), ...
'For constant, the only allowed event is none');
tip='Constant not measured';
str1='&nbsp;&nbsp;&nbsp;k';
stat1='N';
otherwise
assert(strcmp(kind,'condition'), ...
'Must be a condition probe.')
switch event
case 'none'
tip='Condition not covered';
str1='&nbsp;&nbsp;=&gt;';
stat1='F';
case 'full'
tip='Condition covered true and false';
str1='tf&nbsp;&nbsp;';
stat1='T';
case 'true'
tip='Condition covered true, but not false';
str1='&nbsp;=&gt;';
stat1='F';
str2='t';
stat2='T';
case 'false'
tip='Condition covered false, but not true';
str1='&nbsp;=&gt;';
stat1='F';
str2='f';
stat2='T';
end
end
textStr1{i} = str1;
textStr1Status{i} = stat1;
textStr2{i} = str2;
textStr2Status{i} = stat2;
toolTips{i} = tip;

end

end

function setSrcFile(this, file, componentDir, lInstrumentedObjectCodeFolder)

this.Kinds = [];
this.Events = [];
this.Columns = [];
this.Lines = [];
this.FnCov = [];
this.FnTotal = [];
this.CdCov = [];
this.CdTotal = [];

if ~strcmp(componentDir, this.ComponentDir)
% Load coverage results for this model component
componentDirRel = componentDir(length(this.CodeGenFolder)+2:end);
objFolder = fullfile(componentDirRel, lInstrumentedObjectCodeFolder);
lComponentObjDirIdx = ...
strcmp(this.ComponentObjDirs, objFolder);

assert(sum(lComponentObjDirIdx)==1, 'Must be one match for component folder');
this.ComponentObjDir = this.ComponentObjDirs{lComponentObjDirIdx};
resultsFile = coder.coverage.CoverageResultsFile...
(fullfile(this.CodeGenFolder, this.ComponentObjDir));
resultsFile.load;
this.ComponentCovData = resultsFile.getCovData;
this.ComponentDir = componentDir;
end

foundCoverage = false;
for i=1:length(this.ComponentCovData.srcs)
srcPathFromCovFile = this.ComponentCovData.srcs(i).srcPath;
if ~coder.coverage.CodeCoverageHook.isAbsPath({srcPathFromCovFile})
srcPathFromCovFile = fullfile...
(this.CodeGenFolder, srcPathFromCovFile);
srcPathFromCovFile = RTW.reduceRelativePath...
(srcPathFromCovFile);
end
if ispc
% On Windows, use a special file comparison function to handle case-insensitity
% as well as the possibility that files under matlabroot may
% be subject to drive mapping in the case when matlabroot has spaces
cmpFcn = @(srcPathFromCovFile, srcPathFromBuildInfo)i_filename_cmp...
(this, srcPathFromCovFile, srcPathFromBuildInfo);
else
cmpFcn = @strcmp;
end
if feval(cmpFcn, fullfile(srcPathFromCovFile),...
fullfile(file))
probes = this.ComponentCovData.srcs(i).probes;
fn_cov = this.ComponentCovData.srcs(i).fn_cov;
fn_total = this.ComponentCovData.srcs(i).fn_total;
cd_cov = this.ComponentCovData.srcs(i).cd_cov;
cd_total = this.ComponentCovData.srcs(i).cd_total;
srcPath = this.ComponentCovData.srcs(i).srcPath;
foundCoverage = true;
break
end
end

if ~foundCoverage
this.setAnnotationsAvailable(false);
return;
else
this.setAnnotationsAvailable(true);
end

this.FnCov = fn_cov;
this.FnTotal = fn_total;
this.CdCov = cd_cov;
this.CdTotal = cd_total;

lines = cell(size(probes));
kinds = cell(size(probes));
events = cell(size(probes));
columns = cell(size(probes));
[lines{:}]=deal(probes.line);
[kinds{:}]=deal(probes.kind);
[events{:}]=deal(probes.event);
[columns{:}]=deal(probes.column);

% Replace empty column numbers with zero
[columns{strcmp(columns, '')}] = deal('0');

lines = str2double(lines);
columns = str2double(columns);

[uniqueLines, ~, ic] = unique(lines);
doneFirstAnnotationForLine = false(size(uniqueLines));

% The column number returned by BullseyeCoverage indicates the
% code measured by the probe, not counting whitespace. We need
% to transform this to a column number that includes
% whitespace. To achieve this we first read the source file.
if ~coder.coverage.CodeCoverageHook.isAbsPath({srcPath})
srcPath = fullfile(this.CodeGenFolder,srcPath);
end
srcContents = fileread( srcPath);
srcLines = regexp(srcContents,'([^\n]*)\n','tokens');

columnsActual = zeros(size(columns));
for i=1:length(lines)

doneFirst = doneFirstAnnotationForLine(ic(i));
doneFirstAnnotationForLine(ic(i)) = true;

% Does this annotation need a column pointer
if columns(i) > 0 || doneFirst
srcLine=srcLines{lines(i)}{1};
spaceMapping = cumsum(srcLine~=' ');
columnsActual(i) = find(spaceMapping==(columns(i)+1), 1 );
else
columnsActual(i) = columns(i);
end
end

this.Kinds = kinds;
this.Lines = lines;
this.Events = events;
this.Columns = columnsActual;

end

function this = BullseyeAnnotations(lCodeGenFolder, lComponentObjDirs)
this.CodeGenFolder = lCodeGenFolder;
this.ComponentObjDirs = cell(size(lComponentObjDirs));
for i=1:length(lComponentObjDirs)
% Paths must use file separator for the current platform
this.ComponentObjDirs{i} = fullfile(lComponentObjDirs{i});
end
end

end
end