classdef TestbedAnnotations < 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-2019 The MathWorks, Inc.

properties (Access=private, Constant=true)
HeadingsData = {
'fcnCall', 'PROCEDURE/FUNCTION CALL COVERAGE PROFILE'
'statement', 'STATEMENT COVERAGE PROFILE'
'branchDecision', 'BRANCH/DECISION COVERAGE PROFILE'
'notUsed', 'LINEAR CODE SEQUENCE AND JUMP COVERAGE PROFILE'
'branchCondition', 'BRANCH CONDITION COVERAGE PROFILE'
'mcdc', 'BRANCH CONDITION LIST'
'notUsed', 'MODIFIED CONDITION/DECISION PROFILE FOR PROCEDURE'
'notUsed', 'STATEMENT EXECUTION HISTORY SUMMARY'
'branchDecisionSummary', 'BRANCH/DECISION EXECUTION HISTORY SUMMARY'
'notUsed', 'LCSAJ EXECUTION HISTORY SUMMARY'
'branchConditionSummary','BRANCH CONDITION COVERAGE SUMMARY'
'notUsed', 'BRANCH CONDITION COMBINATION COVERAGE SUMMARY'
'mcdcSummary', 'MODIFIED CONDITION DECISION COVERAGE SUMMARY'
'notUsed', 'BOOLEAN ASSIGNMENT COVERAGE SUMMARY'
'notUsed', 'SUMMARY OF EFFECT OF CURRENT TEST DATA SET ON THE COVERAGE METRICS'
};
HeadingLabels = coder.coverage.TestbedAnnotations.HeadingsData(:,1);
HeadingsText = coder.coverage.TestbedAnnotations.HeadingsData(:,2);
end

properties (Access=private)
LineNoMapOrig;
SrcFile;
DynFile;
NdxFile;
RefSrcFile;
McdcAnalysis;
BdcAnalysis;
BccAnalysis;
StatAnalysis;
FcnAnalysis;
FcnAnalysisAvailable;
Qqqbranches;
Src;
Src_ColNos;
Src_LineNos;
SrcLineExactMatchAgainstRefSrc;
RefSrc;
Ref_LineNos;
Ref_SrcIdx;
TestbedAnalysis;
InstrPrefix;
end

methods(Access=private, Static=true)

function [lineNosFrom, lineNosTo, covered] = parseBdcSection...
(bdc_section)

separatorLine = '^\s*(===========.*?$)';

[s, e] = regexp(bdc_section, separatorLine, 'lineanchors');

if length(s)==2
% No branches in this section of code
lineNosFrom = []; lineNosTo=[]; covered=[];
return;
end

% xxx(g768449 we may get "Unexpected branches")
%
% assert(length(s)==4, ...
% 'The branch/decision coverage data must have the expected format');

bdcData = bdc_section(e(2)+1:s(3)-1);

bdcResult = '([0-9]+)\s*\**\s*?';
bdcExpr = ['\s*([-0-9]+)\s+([-0-9]+)\s*' bdcResult bdcResult bdcResult '$' ];

toks = regexp(bdcData, bdcExpr, 'tokens', 'lineanchors');

numResults = length(toks);
lineNosFrom = zeros(size(toks));
lineNosTo = zeros(size(toks));
covered = zeros(size(toks));
for i=1:numResults
lineNosFrom(i) = str2double(toks{i}{1});
lineNosTo(i) = str2double(toks{i}{2});
covered(i) = str2double(toks{i}{5});
end

[lineNosFrom, sortIdx] = sort(lineNosFrom);
lineNosTo = lineNosTo(sortIdx);
covered = covered(sortIdx);

end

function [lineNos, conditionText, numTrue, numFalse] = parseBccSection...
(bcc_section)

separatorLine = '^\s*(===========.*?$)';

[s, e] = regexp(bcc_section, separatorLine, 'lineanchors');

if length(s)==2
% No branch condition coverage to measure in this section of code
lineNos = []; numTrue = []; numFalse = [];
return;
end

assert(length(s)==4, ...
'The branch/condition coverage data must have the expected format');

bccData = bcc_section(e(2)+1:s(3)-1);
bccResult = '([0-9]+)\s*\**\s*?';
bccExpr = ['\s*([-0-9]+)\s+(.*?)\s*(T|F)\s*' bccResult bccResult bccResult '$' ];

toks = regexp(bccData, bccExpr, 'tokens', 'lineanchors');

numResults = length(toks);
allLineNos = zeros(size(toks));
allNumEvents = zeros(size(toks));
allTrueFalse = cell(size(toks));
allConditionText = cell(size(toks));
for i=1:numResults
allLineNos(i) = str2double(toks{i}{1});
allConditionText(i) = strtrim(toks{i}(2));
allTrueFalse(i) = toks{i}(3);
allNumEvents(i) = str2double(toks{i}{6});
end

[lineNos, ~, uniqueIdx] = unique(allLineNos);
assert(length(lineNos)==numResults/2,...
'There must be one condition per reformatted line and it must have two outcomes');

numTrue = zeros(size(lineNos));
numFalse = zeros(size(lineNos));
conditionText = cell(size(lineNos));
for i=1:length(lineNos)
trueFalse = allTrueFalse(uniqueIdx==i);
numEvents = allNumEvents(uniqueIdx==i);
trueIdx = strcmp(trueFalse, 'T');
numTrue(i) = numEvents(trueIdx);
assert(all(strcmp(trueFalse{~trueIdx},'F')), ...
'Outcome for each condition is either T or F');
numFalse(i) = numEvents(~trueIdx);
text = allConditionText(uniqueIdx==i);
assert(strcmp(text{1}, text{2}), 'Condition text must match');
conditionText{i} = text{1};
end
[lineNos, sortIdx] = sort(lineNos);
numTrue = numTrue(sortIdx);
numFalse = numFalse(sortIdx);
end

function [lineNos, numCalls] = parseStatSection...
(stat_section)

separatorLine = '^\s*(===========.*?$)';

[s, e] = regexp(stat_section, separatorLine, 'lineanchors');

if length(s)==2
% No statement condition coverage to measure in this section of code
lineNos = []; numCalls = [];
return;
end

assert(length(s)==4, ...
'The statement coverage data must have the expected format');

statData = stat_section(e(2)+1:s(3)-1);

statResult = '(([0-9]+)|-)\s*\**\s*';
statExpr = ['\s*([0-9]+)\s+(.*?)' statResult statResult statResult '$' ];
toks = regexp(statData, statExpr, 'tokens', 'lineanchors');

numResults = length(toks);
lineNos = zeros(size(toks));
numCalls = zeros(size(toks));
keepIdx = true(size(lineNos));
for i=1:numResults
lineNos(i) = str2double(toks{i}{1});
numCalls(i) = str2double(toks{i}{5});
if ~isfinite(numCalls(i))
keepIdx(i)=false;
end
end
% filter out lines with no statements to execute
lineNos = lineNos(keepIdx);
numCalls = numCalls(keepIdx);

[lineNos, sortIdx] = sort(lineNos);
numCalls = numCalls(sortIdx);
end

function [lineNos, conditionText, indep] = parseMcdcSection(mcdc_section)

mcdcHeading = 'MODIFIED CONDITION/DECISION PROFILE';

headingIdx = strfind(mcdc_section, mcdcHeading);

assert(length(headingIdx)==1,...
['The MCDC heading must appear exactly once '...
'within each branch/condition list section']);

lineNosSection = mcdc_section(1:headingIdx-1);
indepSection = mcdc_section(headingIdx:end);

lineNosExpr = '^\s*C([0-9]+)\s+([0-9]+)\s+(.*?)$';
lineNoTokens = regexp(lineNosSection, lineNosExpr, 'tokens',...
'lineanchors');

lineNos = double(size(lineNoTokens));
conditionNosL = double(size(lineNos));
conditionText = cell(size(lineNos));
for i=1:length(lineNoTokens)
lineNos(i)=str2double(lineNoTokens{i}{2});
conditionNosL(i)=str2double(lineNoTokens{i}{1});
conditionText{i}=strtrim(lineNoTokens{i}{3});
end

% MCDC result takes form either "NOT SHOWN" or a pair of
% table indices, e.g. "1 3".
resultExpr = '([0-9]+\s+[0-9]+)|(NOT SHOWN)';
indepExpr = ['^\s*([0-9]+)' ...
'\s+(' resultExpr ')' ...
'\s+(' resultExpr ')' ...
'\s+(' resultExpr ')' ...
'\s*$'];

indepToks = regexp(indepSection, indepExpr, 'tokens', 'lineanchors');

indep = false(size(indepToks));
assert(length(indep)==length(lineNos),...
'There must be one MCDC result for each condition');
for i=1:length(indep)
combinedResultColNo=4;
result = indepToks{i}{combinedResultColNo};
if ~strcmp(result, 'NOT SHOWN')
assert(length(str2num(result))==2,...
'Result must be a pair of table indices'); %#ok<ST2NM>
indep(i)=true;
end
conditionNumber=str2double(indepToks{i}{1});
assert(conditionNumber==conditionNosL(i),...
'Condition numbers must match');
end

end

function [lineNos, fcnNames, numCalls] = parseFcnSection(fcn_section)

separatorLine = '^\s*(===========.*?$)';

[s, e] = regexp(fcn_section, separatorLine, 'lineanchors');

if length(s)==2
% No functions in this section of code
lineNos = [];
fcnNames = {};
numCalls = [];
return;
end

assert(length(s)==4, ...
'The function call data must have the expected format');

fcnData = fcn_section(e(2)+1:s(3)-1);

fcn_identifier = '([_A-Za-z][_A-Za-z0-9:~]*)';
fcnResult = '([0-9]+)\s*\**\s*?';
fcnExpr = ['\s*([-0-9]+)\s+' fcn_identifier ...
'\s+(-[0-9]+)\s*' fcnResult fcnResult fcnResult '$' ];

toks = regexp(fcnData, fcnExpr, 'tokens', 'lineanchors');

numResults = length(toks);
lineNos = zeros(size(toks));
numCalls = zeros(size(toks));
fcnNames = cell(size(toks));
for i=1:numResults
lineNos(i) = str2double(toks{i}{1});
fcnNames{i} = toks{i}{2};
numCalls(i) = str2double(toks{i}{6});
end

[lineNos, sortIdx] = sort(lineNos);
numCalls = numCalls(sortIdx);
fcnNames = fcnNames(sortIdx);

end

end

methods (Access=private)

function [lineNoOrig, colNoOrig] = getOrigLineNo...
(this, reformattedLineNo)

refSrcIdx = find(this.Ref_LineNos==reformattedLineNo, 1 );
if isempty(refSrcIdx)
% The reformatted source code must be white space only
% therefore we must return location data for the closest
% preceding non-white space character
refSrcIdx = find(this.Ref_LineNos<=reformattedLineNo, 1, 'last' );
end
srcIdx = this.Ref_SrcIdx(refSrcIdx);
if srcIdx ~= -1
lineNoOrig = this.Src_LineNos(srcIdx);
colNoOrig = this.Src_ColNos(srcIdx);
else
lineNoOrig = this.LineNoMapOrig(reformattedLineNo);
colNoOrig = -1;
end

end

function [srcLineNo, srcColNo] = findCodeFragment...
(this, codeFragment, refLineNoStart, searchForward)

% remove white space
codeFragment = regexprep(codeFragment, '\s', '');

if searchForward
refStartIdx = find(this.Ref_LineNos==refLineNoStart, 1 );
matchIdx = min(strfind(this.RefSrc(refStartIdx:end), codeFragment));
if ~isempty(matchIdx)
refEndIdx = refStartIdx+matchIdx-1+length(codeFragment)-1;
srcMatchIdx = this.Ref_SrcIdx(refStartIdx + matchIdx - 1);
end
else
refEndIdx = find(this.Ref_LineNos==refLineNoStart, 1, 'last' );
matchIdx = max(strfind(this.RefSrc(1:refEndIdx), codeFragment));
if ~isempty(matchIdx)
refStartIdx = matchIdx;
srcMatchIdx = this.Ref_SrcIdx(matchIdx);
end
end

if ~isempty(matchIdx)
% For a valid match (unaffected by macro expansion) the reformatted and original
% source must be identical
srcStartIdx = this.Ref_SrcIdx(refStartIdx);
srcEndIdx = this.Ref_SrcIdx(refEndIdx);
if srcStartIdx>0 && srcEndIdx > 0 && ...
(srcEndIdx-srcStartIdx == refEndIdx-refStartIdx) && ...
strcmp(this.RefSrc(refStartIdx:refEndIdx),...
this.Src(srcStartIdx:srcEndIdx))
validMatch = true;
else
validMatch = false;
end
end

if validMatch
srcColNo = this.Src_ColNos(srcMatchIdx);
srcLineNo = this.Src_LineNos(srcMatchIdx);
else
srcColNo = -1;
srcLineNo = -1;
end

end

function updateLineNosMap(this)

lSrcFileContent = fileread(this.SrcFile);

startOfLine = regexp(lSrcFileContent, '^.', 'lineanchors');
startOfLine(end+1)=1+length(lSrcFileContent);
srcLineNoMap = zeros(size(startOfLine));
srcColNoMap = zeros(size(startOfLine));
for i=1:length(startOfLine)-1
srcLineNoMap(startOfLine(i):startOfLine(i+1)-1)=i;
srcColNoMap(startOfLine(i):startOfLine(i+1)-1) = ...
1:startOfLine(i+1)-startOfLine(i);
end
spaceIdx = regexp(lSrcFileContent, '\s');
keepIdx = true(size(lSrcFileContent));
keepIdx(spaceIdx) = false;
this.Src=lSrcFileContent(keepIdx);
this.Src_LineNos=srcLineNoMap(keepIdx);
this.Src_ColNos=srcColNoMap(keepIdx);

lRefSrcFileContent = fileread(this.RefSrcFile);

% remove the reformatted source annotation at start of each line
lRefSrcFileClean = regexprep(lRefSrcFileContent, ...
'^(\s*[0-9]+(T|F))', '', 'lineanchors');
startOfLine = regexp(lRefSrcFileClean, '^.', 'lineanchors');
startOfLine(end+1)=1+length(lRefSrcFileClean);
refSrcLineNoMap = zeros(size(startOfLine));
for i=1:length(startOfLine)-1
refSrcLineNoMap(startOfLine(i):startOfLine(i+1)-1)=i;
end
spaceIdx = regexp(lRefSrcFileClean, '\s');
keepIdx = true(size(lRefSrcFileClean));
keepIdx(spaceIdx) = false;
this.RefSrc=lRefSrcFileClean(keepIdx);
this.Ref_LineNos=refSrcLineNoMap(keepIdx);

this.LineNoMapOrig = ...
coder.coverage.TestbedAnalysis.readLineNoMap(this.NdxFile, lSrcFileContent);

this.Ref_SrcIdx = -1 * ones(size(this.RefSrc));

numSrcLines = this.Src_LineNos(end);

this.SrcLineExactMatchAgainstRefSrc = false(numSrcLines, 1);

for srcLineIdx=1:numSrcLines

% Find reformatted lines that map to this source line
refSrcLineNos = find(this.LineNoMapOrig==srcLineIdx);

if ~isempty(refSrcLineNos)
refSrcIdxStart = find(this.Ref_LineNos>=min(refSrcLineNos), 1 );
refSrcIdxEnd = find(this.Ref_LineNos<=max(refSrcLineNos), 1, 'last' );

% Look for an exact match between reformatted and original source
srcLine = this.Src(this.Src_LineNos==srcLineIdx);
lenSrcLine = length(srcLine);
refSrcSearchRange = (max(1, (refSrcIdxStart-lenSrcLine+1))):...
min(length(this.RefSrc),(refSrcIdxEnd+lenSrcLine-1));
searchStr = this.RefSrc(refSrcSearchRange);
matchIdx = strfind(searchStr, srcLine);

if (length(matchIdx)>1)
% One circumstance where this can occur is that
% Testbed corrects bad programming style by inserting
% {} around any statement following a branch
matchIdx=[];
end

if ~isempty(matchIdx)
refMatchRange = refSrcSearchRange(matchIdx:(matchIdx+lenSrcLine-1));

% For the matched source insert mapping data to transform from reformatted back
% to the original source
this.Ref_SrcIdx(refMatchRange) = find(this.Src_LineNos==srcLineIdx);

this.SrcLineExactMatchAgainstRefSrc(srcLineIdx)=true;
end
end
end

end

function [lineNosOrig, colNosOrig, collapse] = ...
getOrigLineNos(this, reformattedLineNos, ...
codeTextSnippets, searchForward)

lineNosOrig = zeros(size(reformattedLineNos));
colNosOrig = zeros(size(reformattedLineNos));
collapse = false(size(reformattedLineNos));

for i=1:length(reformattedLineNos)
if nargin < 3
[lineNoOrig, colNoOrig] = ...
getOrigLineNo(this, reformattedLineNos(i));
else
[lineNoOrig, colNoOrig] = this.findCodeFragment...
(codeTextSnippets{i}, reformattedLineNos(i), ...
searchForward);

if lineNoOrig < 0
% Couldn't find a match (e.g. line contained
% macros)
[lineNoOrig, colNoOrig] = ...
getOrigLineNo(this, reformattedLineNos(i));
end
end

lineNosOrig(i) = lineNoOrig;
colNosOrig(i) = colNoOrig;
if lineNoOrig > 0
collapse(i)= ~this.SrcLineExactMatchAgainstRefSrc(lineNoOrig);
end
end

end

end

methods (Access=public)

function summary = getSummaryHtml(this)

labels = {}; bars = {};

if ~isempty(this.Qqqbranches)
total = this.Qqqbranches;
covered = 0;
[labels{end+1}, bars{end+1}] = coder.coverage.Annotations.getHtmlSummaryBar...
(covered,total,'CoderCoverage:LDRA:LdraOverallCoverageLabel',...
'CoderCoverage:LDRA:LdraOverallCoverageTooltip');

else

% Function call summary
if this.FcnAnalysisAvailable
tmp = cell(size(this.FcnAnalysis));
if ~isempty(tmp)
[tmp{:}] = deal(this.FcnAnalysis.numCalls);
tmp=logical(cell2mat(tmp));
covered = sum(tmp);
total = length(tmp);
else
covered = 0;
total = 0;
end
[labels{end+1}, bars{end+1}] = coder.coverage.Annotations.getHtmlSummaryBar...
(covered,total,'CoderCoverage:LDRA:LdraFunctionCoverageLabel',...
'CoderCoverage:LDRA:LdraFunctionCoverageTooltip');
end

% Statement summary
tmp = cell(size(this.StatAnalysis));
if ~isempty(tmp)
[tmp{:}] = deal(this.StatAnalysis.numCalls);
tmp=logical(cell2mat(tmp));
covered = sum(tmp);
total = length(tmp);
else
covered = 0;
total = 0;
end
[labels{end+1}, bars{end+1}] = coder.coverage.Annotations.getHtmlSummaryBar...
(covered,total,'CoderCoverage:LDRA:StatementCoverageLabel',...
'CoderCoverage:LDRA:StatementCoverageTooltip');

% Branch/condition summary
tmp = cell(size(this.BccAnalysis));
if ~isempty(tmp)
[tmp{:}] = deal(this.BccAnalysis.numTrue);
tmp=logical(cell2mat(tmp));
coveredTrue = sum(tmp);
tmp = cell(size(this.BccAnalysis));
[tmp{:}] = deal(this.BccAnalysis.numFalse);
tmp=logical(cell2mat(tmp));
coveredFalse = sum(tmp);
covered = coveredTrue+coveredFalse;
total = length(tmp)*2;
else
covered = 0;
total = 0;
end
[labels{end+1}, bars{end+1}] = coder.coverage.Annotations.getHtmlSummaryBar...
(covered, total, 'CoderCoverage:LDRA:LdraBccCoverageLabel',...
'CoderCoverage:LDRA:LdraBccCoverageTooltip');

% Branch/decision summary
if ~isempty(this.BdcAnalysis)
total = length(this.BdcAnalysis(:,1));
covered = sum(logical(this.BdcAnalysis(:,3)));
else
covered = 0;
total = 0;
end
[labels{end+1}, bars{end+1}] = coder.coverage.Annotations.getHtmlSummaryBar...
(covered, total, 'CoderCoverage:LDRA:LdraBdcCoverageLabel',...
'CoderCoverage:LDRA:LdraBdcCoverageTooltip');

% MC/DC summary
if ~isempty(this.McdcAnalysis)
tmp = cell(size(this.McdcAnalysis));
[tmp{:}] = deal(this.McdcAnalysis.indep);
covered=sum(logical(cell2mat(tmp)));
total = length(tmp);
else
covered = 0;
total = 0;
end
[labels{end+1}, bars{end+1}] = coder.coverage.Annotations.getHtmlSummaryBar...
(covered, total, 'CoderCoverage:LDRA:McdcCoverageLabel',...
'CoderCoverage:LDRA:McdcCoverageTooltip');
end

% Fill out with empty strings if required
[labels{end+1:5}] = deal('');
[bars{end+1:5}] = deal('');

% Use width:0 to prevent the summary table being filled out to 100% width
% Use white-space:nowrap to prevent the content of each cell from being wrapped
summary = sprintf(['%s\n<p><table style="width:0;white-space:nowrap"><tr><td>%s</td><td>%s</td>'...
'<td>&nbsp;&nbsp;&nbsp;&nbsp;%s</td><td>%s</td>'...
'<td>&nbsp;&nbsp;&nbsp;&nbsp;%s</td><td>%s</td></tr>\n'...
'<tr><td>%s</td><td>%s</td>'...
'<td>&nbsp;&nbsp;&nbsp;&nbsp;%s</td><td>%s</td></tr></table>\n<p>'],...
DAStudio.message('CoderCoverage:LDRA:CoverageSummary'), ...
labels{1}, bars{1}, ...
labels{2}, bars{2}, ...
labels{3}, bars{3}, ...
labels{4}, bars{4}, ...
labels{5}, bars{5});

end

function [lineNosOrig, colNosOrig, collapse, text1, text1Status,...
text2, text2Status, toolTips] = getBdcAnnotations(this)

if ~isempty(this.BdcAnalysis)
lineNosFrom = this.BdcAnalysis(:,1);
lineNosTo = this.BdcAnalysis(:,2);
covered = this.BdcAnalysis(:,3);
else
lineNosFrom = [];
lineNosTo = [];
covered = [];
end

lineNosToPosIdx = (lineNosTo>0);

[lineNosUnique, ~, in] = unique(lineNosFrom);

numUnique=length(lineNosUnique);

[lineNosOrig, colNosOrig, collapse] = ...
this.getOrigLineNos(lineNosUnique);

lineNosToOrig = lineNosTo;

[lineNosToOrigPosIdx, colNosToOrigPosIdx] = ...
this.getOrigLineNos(lineNosTo(lineNosToPosIdx));

lineNosToOrig(lineNosToPosIdx) = lineNosToOrigPosIdx;
colNosToOrig = zeros(size(lineNosToOrig));
colNosToOrig(lineNosToPosIdx) = colNosToOrigPosIdx;

text1 = cell(numUnique, 1);
[text1{1:numUnique}] = deal('');
text2 = text1;
text1Status = text1;
text2Status = text1;
toolTips = text1;

for k=1:numUnique
k_idx = (in==k);
lineNosTo_k = lineNosToOrig(k_idx);
colNosTo_k = colNosToOrig(k_idx);
covered_k = covered(k_idx);

if all(covered_k)
toolTips{k} = DAStudio.message('CoderCoverage:LDRA:LdraAnnotBranchCovered');
text1{k} = '&nbsp;&nbsp;&nbsp;b';
text1Status{k} = 'T';
elseif ~any(covered_k)
toolTips{k} = DAStudio.message('CoderCoverage:LDRA:LdraAnnotBranchNotCovered');
text1{k} = '&nbsp;&nbsp;=&gt;';
text1Status{k} = 'F';
else
branchDsts = cell(size(covered_k));
for ii6=1:length(branchDsts)
if colNosTo_k(ii6)>0
% Include column number in destination
colStr = sprintf('c%d', colNosTo_k(ii6));
else
colStr = '';
end
branchDsts{ii6} = sprintf('%d%s', lineNosTo_k(ii6), colStr);
end

coveredIdx = logical(covered_k);

coveredDsts = sprintf('%s, ', branchDsts{coveredIdx});
notCoveredDsts = sprintf('%s, ', branchDsts{~coveredIdx});

toolTips{k} = DAStudio.message...
('CoderCoverage:LDRA:LdraAnnotBranchPartiallyCovered',...
coveredDsts(1:end-2), notCoveredDsts(1:end-2));

text1{k} = '&nbsp;=&gt;';
text1Status{k} = 'F';
text2{k} = 'b';
text2Status{k} = 'T';
end
end
end

function [lineNos, colNos, collapse, textStrsT, textStrsTStatus,...
textStrsF, textStrsFStatus, toolTips] = getMcdcAnnotations(this)

mcdc = this.McdcAnalysis(:);
n = length(mcdc);
tmp = cell(size(mcdc));
if ~isempty(this.McdcAnalysis)
[tmp{:}] = deal(mcdc.lineNosReformatted);
lineNosReformatted = cell2mat(tmp);
[tmp{:}] = deal(mcdc.indep);
independence = cell2mat(tmp);
[conditionText{1:n}] = deal(mcdc.conditionText);
searchForward = true;
[lineNos, colNos, collapse] = ...
this.getOrigLineNos(lineNosReformatted, ...
conditionText, searchForward);
else
lineNos = [];
colNos = [];
independence = [];
collapse = [];
end
textStrsT = cell(size(independence));
[textStrsT{independence}] = deal('&nbsp;&nbsp;mc');
[textStrsT{~independence}] = deal('');

textStrsF = cell(size(independence));
[textStrsF{independence}] = deal('');
[textStrsF{~independence}] = deal('=&gt;mc');

textStrsTStatus = cell(size(independence));
[textStrsTStatus{independence}] = deal('T');
[textStrsTStatus{~independence}] = deal('');

textStrsFStatus = cell(size(independence));
[textStrsFStatus{independence}] = deal('');
[textStrsFStatus{~independence}] = deal('F');

toolTips = cell(size(independence));
[toolTips{independence}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotMcdcIndep'));
[toolTips{~independence}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotMcdcNoIndep'));

end

function [lineNos, colNos, collapse, textStrs1, textStrs1Status,...
textStrs2, textStrs2Status, toolTips] = getBccAnnotations(this)

bcc = this.BccAnalysis(:);
n = length(bcc);
tmp = cell(size(bcc));
if ~isempty(bcc)
[tmp{:}] = deal(bcc.lineNosReformatted);
lineNosReformatted = cell2mat(tmp);
[tmp{:}] = deal(bcc.numTrue);
covered_true = logical(cell2mat(tmp));
[tmp{:}] = deal(bcc.numFalse);
covered_false = logical(cell2mat(tmp));
[conditionText{1:n}] = deal(bcc.conditionText);
searchForward = false;
[lineNos, colNos, collapse] = ...
this.getOrigLineNos(lineNosReformatted, ...
conditionText, searchForward);
else
lineNos = [];
colNos = [];
covered_true = [];
covered_false = [];
collapse = [];
end

fullIdx = covered_true & covered_false;
noneIdx = ~covered_true & ~covered_false;
trueOnlyIdx = covered_true & ~covered_false;
falseOnlyIdx = ~covered_true & covered_false;

textStrs1 = cell(size(lineNos));
[textStrs1{fullIdx}] = deal('&nbsp;&nbsp;tf');
[textStrs1{trueOnlyIdx}] = deal('&nbsp;=&gt;');
[textStrs1{falseOnlyIdx}] = deal('&nbsp;=&gt;');
[textStrs1{noneIdx}] = deal('&nbsp;&nbsp;=&gt;');

textStrs1Status = cell(size(lineNos));
[textStrs1Status{fullIdx}] = deal('T');
[textStrs1Status{trueOnlyIdx}] = deal('F');
[textStrs1Status{falseOnlyIdx}] = deal('F');
[textStrs1Status{noneIdx}] = deal('F');

textStrs2 = cell(size(lineNos));
[textStrs2{fullIdx}] = deal('');
[textStrs2{trueOnlyIdx}] = deal('t');
[textStrs2{falseOnlyIdx}] = deal('f');
[textStrs2{noneIdx}] = deal('');

textStrs2Status = cell(size(lineNos));
[textStrs2Status{fullIdx}] = deal('');
[textStrs2Status{trueOnlyIdx}] = deal('T');
[textStrs2Status{falseOnlyIdx}] = deal('T');
[textStrs2Status{noneIdx}] = deal('');

toolTips = cell(size(lineNos));

[toolTips{fullIdx}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotConditionFullyCovered'));
[toolTips{trueOnlyIdx}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotConditionTrueOnly'));
[toolTips{falseOnlyIdx}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotConditionFalseOnly'));
[toolTips{noneIdx}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotConditionNotCovered'));

end

function [lineNos, colNos, collapse, textStrs1, textStrs1Status,...
textStrs2, textStrs2Status, toolTips] = getStatAnnotations(this)

stat = this.StatAnalysis(:);
tmp = cell(size(stat));
if ~isempty(stat)
[tmp{:}] = deal(stat.lineNosReformatted);
lineNosReformatted = cell2mat(tmp);
[tmp{:}] = deal(stat.numCalls);
numCalls = logical(cell2mat(tmp));
[allLineNos, ~, collapse] = ...
this.getOrigLineNos(lineNosReformatted);
else
allLineNos = [];
numCalls = [];
collapse = [];
end

[lineNos, ia, ic] = unique(allLineNos);
colNos = zeros(size(lineNos)); % We don't provide column number information
collapse = collapse(ia);

covered = false(size(lineNos));
for i=1:length(lineNos)
idx_i = (ic==i);
numCalls_i = numCalls(idx_i);
covered(i) = all(numCalls_i);
end

textStrs1 = cell(size(lineNos));
[textStrs1{1:end}] = deal('&nbsp;&nbsp;&nbsp;&#8226;');

textStrs1Status = cell(size(lineNos));
[textStrs1Status{covered}] = deal('T');
[textStrs1Status{~covered}] = deal('F');

textStrs2 = cell(size(lineNos));
[textStrs2{1:end}] = deal('');

textStrs2Status = cell(size(lineNos));
[textStrs2Status{1:end}] = deal('');

toolTips = cell(size(lineNos));

[toolTips{covered}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotStatementsCovered'));
[toolTips{~covered}] = deal...
(DAStudio.message('CoderCoverage:LDRA:LdraAnnotStatementsNotCovered'));

end

function [lineNos, colNos, collapse, textStrsT, textStrsTStatus,...
textStrsF, textStrsFStatus, toolTips] = getFcnAnnotations(this)

fcn = this.FcnAnalysis(:);
n = length(fcn);
tmp = cell(size(fcn));
if ~isempty(fcn)
[tmp{:}] = deal(fcn.lineNosReformatted);
lineNosReformatted = cell2mat(tmp);
[tmp{:}] = deal(fcn.numCalls);
covered = logical(cell2mat(tmp));
[fcnNames{1:n}] = deal(fcn.fcnNames);
[lineNos, colNos, collapse] = ...
this.getOrigLineNos(lineNosReformatted);
else
lineNos = [];
colNos = [];
covered = [];
collapse = [];
end

textStrsT = cell(size(covered));
[textStrsT{covered}] = deal('&nbsp;Fcn');
[textStrsT{~covered}] = deal('');

textStrsF = cell(size(covered));
[textStrsF{covered}] = deal('');
[textStrsF{~covered}] = deal('&nbsp;&nbsp;=&gt;');

textStrsTStatus = cell(size(covered));
[textStrsTStatus{covered}] = deal('T');
[textStrsTStatus{~covered}] = deal('');

textStrsFStatus = cell(size(covered));
[textStrsFStatus{covered}] = deal('');
[textStrsFStatus{~covered}] = deal('F');

toolTips = cell(size(covered));
for i=1:length(toolTips)
if(covered(i))
toolTips{i} = DAStudio.message...
('CoderCoverage:LDRA:LdraAnnotFcnCovered', fcnNames{i});
else
toolTips{i} = DAStudio.message...
('CoderCoverage:LDRA:LdraAnnotFcnNotCovered', fcnNames{i});
end
end
end

function [lineNos, colNos, textStrs1, textStrs1Status,...
textStrs2, textStrs2Status, toolTips] = getAnnotations(this)

[lMcdc, cMcdc, collapseMcdc, t1Mcdc, s1Mcdc, t2Mcdc, s2Mcdc, tipsMcdc] = ...
getMcdcAnnotations(this);

[lBdc, cBdc, collapseBdc, t1Bdc, s1Bdc, t2Bdc, s2Bdc, tipsBdc] = ...
getBdcAnnotations(this);

[lFcn, cFcn, collapseFcn, t1Fcn, s1Fcn, t2Fcn, s2Fcn, tipsFcn] = ...
getFcnAnnotations(this);

[lBcc, cBcc, collapseBcc, t1Bcc, s1Bcc, t2Bcc, s2Bcc, tipsBcc] = ...
getBccAnnotations(this);

[lStat, cStat, collapseStat, t1Stat, s1Stat, t2Stat, s2Stat, tipsStat] = ...
getStatAnnotations(this);

lineNos = [lMcdc(~collapseMcdc); lBdc(~collapseBdc); lFcn(~collapseFcn); lBcc(~collapseBcc); lStat(~collapseStat)];
colNos = [cMcdc(~collapseMcdc); cBdc(~collapseBdc); cFcn(~collapseFcn); cBcc(~collapseBcc); cStat(~collapseStat)];
textStrs1 = [t1Mcdc(~collapseMcdc); t1Bdc(~collapseBdc); t1Fcn(~collapseFcn); t1Bcc(~collapseBcc); t1Stat(~collapseStat)];
textStrs1Status = [s1Mcdc(~collapseMcdc); s1Bdc(~collapseBdc); s1Fcn(~collapseFcn); s1Bcc(~collapseBcc); s1Stat(~collapseStat)];
textStrs2 = [t2Mcdc(~collapseMcdc); t2Bdc(~collapseBdc); t2Fcn(~collapseFcn); t2Bcc(~collapseBcc); t2Stat(~collapseStat)];
textStrs2Status = [s2Mcdc(~collapseMcdc); s2Bdc(~collapseBdc); s2Fcn(~collapseFcn); s2Bcc(~collapseBcc); s2Stat(~collapseStat)];
toolTips = [tipsMcdc(~collapseMcdc); tipsBdc(~collapseBdc); tipsFcn(~collapseFcn); tipsBcc(~collapseBcc); tipsStat(~collapseStat)];

% For any coverage measured on a line where the the reformatted source does not
% exactly match the original source, we collapse the information to
% a single annotation on this line in the original source file. This
% may occur due to included source files, use of Macros or when
% additional brackets {} are inserted into the reformatted source
[collapseLineNos, ~, ib] = unique([lMcdc(collapseMcdc); lBdc(collapseBdc); lFcn(collapseFcn); lBcc(collapseBcc);...
lStat(collapseStat)]);

collapseStrs1 = cell(size(collapseLineNos));
collapseStrs1Status = cell(size(collapseLineNos));
collapseStrs2 = cell(size(collapseLineNos));
collapseStrs2Status = cell(size(collapseLineNos));
collapseTooltips = cell(size(collapseLineNos));
collapseColNos = zeros(size(collapseLineNos));

statusStrs1CollapseAll = [s1Mcdc(collapseMcdc); s1Bdc(collapseBdc); s1Fcn(collapseFcn); s1Bcc(collapseBcc); s1Stat(collapseStat)];
statusStrs2CollapseAll = [s2Mcdc(collapseMcdc); s2Bdc(collapseBdc); s2Fcn(collapseFcn); s2Bcc(collapseBcc); s2Stat(collapseStat)];
for i=1:length(collapseLineNos)
collapseLineNoIdx = (ib==i);
statusStrs = [statusStrs1CollapseAll(collapseLineNoIdx);
statusStrs2CollapseAll(collapseLineNoIdx)];

covered = any(strcmp(statusStrs,'T'));
notCovered = any(strcmp(statusStrs, 'F'));

collapseStrs2{i}='';
collapseStrs2Status{i}='';
if ~any(covered)
% No probes were covered
collapseTooltips{i} = DAStudio.message...
('CoderCoverage:LDRA:LdraAnnotIncludeFileNoCoverage');
collapseStrs1{i}='&nbsp;=&gt;&Sigma;';
collapseStrs1Status{i}='F';
elseif any(notCovered)
% Partial coverage
collapseTooltips{i} = DAStudio.message...
('CoderCoverage:LDRA:LdraAnnotIncludePartiallyCovered');
collapseStrs1{i}='&nbsp;=&gt;';
collapseStrs1Status{i}='F';
collapseStrs2{i}='&Sigma;';
collapseStrs2Status{i}='T';
else
% Full coverage
collapseTooltips{i} = DAStudio.message...
('CoderCoverage:LDRA:LdraAnnotIncludeFileCovered');
collapseStrs1{i}='&nbsp;&nbsp;&nbsp;&Sigma;';
collapseStrs1Status{i}='T';
end
end

lineNos = [lineNos; collapseLineNos];
colNos = [colNos; collapseColNos];
textStrs1 = [textStrs1; collapseStrs1];
textStrs1Status = [textStrs1Status; collapseStrs1Status];
textStrs2 = [textStrs2; collapseStrs2];
textStrs2Status = [textStrs2Status; collapseStrs2Status];
toolTips = [toolTips; collapseTooltips];

end

function analyzeMcdc(this, mcdc_sections)

for i=1:length(mcdc_sections)
[lineNosReformatted, conditionText, indep] = ...
coder.coverage.TestbedAnnotations.parseMcdcSection...
(mcdc_sections{i});

if isempty(this.McdcAnalysis)
this.McdcAnalysis = struct...
('lineNosReformatted', {}, ...
'conditionText', {}, ...
'indep', {});
end

this.McdcAnalysis(end+1:end+length(indep)) ...
= struct...
('lineNosReformatted', num2cell(lineNosReformatted(:)), ...
'conditionText', conditionText(:), ...
'indep', num2cell((indep(:))));
end
end

function analyzeBdc(this, bdc_sections)

for i=1:length(bdc_sections)
[lineNosFrom, lineNosTo, covered] = ...
coder.coverage.TestbedAnnotations.parseBdcSection...
(bdc_sections{i});
this.BdcAnalysis = [
this.BdcAnalysis;
lineNosFrom(:) lineNosTo(:) covered(:)];
end
end

function analyzeBcc(this, bcc_sections)

for i=1:length(bcc_sections)
[lineNosReformatted, conditionText, numTrue, numFalse] = ...
coder.coverage.TestbedAnnotations.parseBccSection...
(bcc_sections{i});

if isempty(this.BccAnalysis)
this.BccAnalysis = struct...
('lineNosReformatted', {}, ...
'conditionText', {}, ...
'numTrue', {}, ...
'numFalse', {});
end

this.BccAnalysis(end+1:end+length(numTrue)) ...
= struct...
('lineNosReformatted', num2cell(lineNosReformatted(:)), ...
'conditionText', conditionText(:), ...
'numTrue', num2cell((numTrue(:))),...
'numFalse', num2cell((numFalse(:))) );

end
end

function analyzeStat(this, stat_sections)

for i=1:length(stat_sections)
[lineNosReformatted, numCalls] = ...
coder.coverage.TestbedAnnotations.parseStatSection...
(stat_sections{i});

if isempty(this.StatAnalysis)
this.StatAnalysis = struct...
('lineNosReformatted', {}, ...
'numCalls', {});
end

this.StatAnalysis(end+1:end+length(numCalls)) ...
= struct...
('lineNosReformatted', num2cell(lineNosReformatted(:)), ...
'numCalls', num2cell(numCalls(:)));
end
end

function analyzeFcn(this, fcn_sections)

if isempty(fcn_sections)
this.FcnAnalysisAvailable = false;
else
this.FcnAnalysisAvailable = true;
end

for i=1:length(fcn_sections)

[lineNosReformatted, fcnNames, numCalls] = ...
coder.coverage.TestbedAnnotations.parseFcnSection...
(fcn_sections{i});

if isempty(this.FcnAnalysis)
this.FcnAnalysis = struct...
('lineNosReformatted', {}, ...
'fcnNames', {}, ...
'numCalls', {});
end

this.FcnAnalysis(end+1:end+length(numCalls)) ...
= struct...
('lineNosReformatted', num2cell(lineNosReformatted(:)), ...
'fcnNames', fcnNames(:), ...
'numCalls', num2cell((numCalls(:))) );

end
end

function this = setSrcFile(this, lSrcFile, ~, ~)

this.LineNoMapOrig = [];
this.SrcFile = lSrcFile;
this.DynFile = [];
this.NdxFile = [];
this.RefSrcFile = [];
this.McdcAnalysis = [];
this.BdcAnalysis = [];
this.BccAnalysis = [];
this.StatAnalysis = [];
this.FcnAnalysis = [];
this.FcnAnalysisAvailable = [];
this.Qqqbranches = [];
this.Src = [];
this.Src_ColNos = [];
this.Src_LineNos = [];
this.RefSrc = [];
this.Ref_LineNos = [];
this.Ref_SrcIdx = [];

if ~this.TestbedAnalysis.isSrcFileInSet(lSrcFile)
this.setAnnotationsAvailable(false);
return;
else
this.setAnnotationsAvailable(true);
end

lDynFile = this.TestbedAnalysis.getDynFile(lSrcFile);

if isempty(lDynFile)
srcFileInstr = coder.coverage.LDRA.getInstrumentedSrcNames...
({lSrcFile}, this.InstrPrefix);

this.Qqqbranches = coder.coverage.LDRA.getNumQqqbranches(srcFileInstr{1});

return;
end

dynContent = fileread(lDynFile);

idxMatch = [];
for i=1:length(this.HeadingsText);
idx = strfind(dynContent, this.HeadingsText{i});
idxMatch = [idxMatch; repmat(i, length(idx), 1), idx(:)]; %#ok
end

% Sort by location in file
[~, sortIdx] = sort(idxMatch(:,2));
idxMatch = idxMatch(sortIdx, :);

% Augment with end of file
idxMatch = [idxMatch; 0 length(lDynFile)+1];

this.SrcFile = lSrcFile;
this.DynFile = lDynFile;
this.NdxFile = strrep(lDynFile, '.dyn', '.ndx');
this.RefSrcFile = strrep(lDynFile, '.dyn', '.ref');

% Generate line number mapping
this.updateLineNosMap;

% Process MC/DC section
mcdc_i = find(strcmp('mcdc', this.HeadingLabels));
mcdc_idx = find(idxMatch(:,1)==mcdc_i);
mcdc_sections = cell(size(mcdc_idx));
for i=1:length(mcdc_idx)
mcdc_sections{i} = dynContent(...
idxMatch(mcdc_idx(i), 2): (idxMatch(mcdc_idx(i)+1, 2)-1)...
);
end
this.analyzeMcdc(mcdc_sections);

% Process branch/decision section
bdc_i = find(strcmp('branchDecision', this.HeadingLabels));
bdc_idx = find(idxMatch(:,1)==bdc_i);
bdc_sections = cell(size(bdc_idx));
for i=1:length(bdc_idx)
bdc_sections{i} = dynContent(...
idxMatch(bdc_idx(i), 2): (idxMatch(bdc_idx(i)+1, 2)-1)...
);
end
this.analyzeBdc(bdc_sections);

% Process function call section
fcn_i = find(strcmp('fcnCall', this.HeadingLabels));
fcn_idx = find(idxMatch(:,1)==fcn_i);
fcn_sections = cell(size(fcn_idx));
for i=1:length(fcn_idx)
fcn_sections{i} = dynContent(...
idxMatch(fcn_idx(i), 2): (idxMatch(fcn_idx(i)+1, 2)-1)...
);
end
this.analyzeFcn(fcn_sections);

% Process branch condition section
bcc_i = find(strcmp('branchCondition', this.HeadingLabels));
bcc_idx = find(idxMatch(:,1)==bcc_i);
bcc_sections = cell(size(bcc_idx));
for i=1:length(bcc_idx)
bcc_sections{i} = dynContent(...
idxMatch(bcc_idx(i), 2): (idxMatch(bcc_idx(i)+1, 2)-1)...
);
end
this.analyzeBcc(bcc_sections);

% Process statement section
stat_i = find(strcmp('statement', this.HeadingLabels));
stat_idx = find(idxMatch(:,1)==stat_i);
stat_sections = cell(size(stat_idx));
for i=1:length(stat_idx)
stat_sections{i} = dynContent(...
idxMatch(stat_idx(i), 2): (idxMatch(stat_idx(i)+1, 2)-1)...
);
end
this.analyzeStat(stat_sections);

end

function this = TestbedAnnotations(lTestbedAnalysis, lInstrPrefix)
this.TestbedAnalysis = lTestbedAnalysis;
this.InstrPrefix = lInstrPrefix;
end

end
end