classdef (Hidden = true) LDRA < coder.coverage.CodeCoverageHook
%LDRA provides code coverage utilities
%
% 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-2020 The MathWorks, Inc.

properties (Access=private)
TestbedFolder;
SetName;
SysearchFile;
TestbedIni;
LockDir;
Workdir;
ContestbedCmd; % Accommodate differences between Linux and Windows platforms
TBiniCmd; % Command to call TBini
TestbedCmd; % Linux we use testbed and Windows, we use Testbed.exe
OptionStartChar;% Accommodate differences between Linux and Windows platforms
ParamStartChar; % Accommodate differences between Linux and Windows platforms
ToolsuiteFile; % Accommodate differences between Linux and Windows platforms
end

properties (Access=private, Constant=true)
% Increment version to force creating a new Testbed folder
VersionId = 1;
end

methods (Access=private, Static=true)

function removeTemporaryFile(fileName, dirsCreated)

% first delete the file
delete(fileName)

% now delete any created folders
for i=1:length(dirsCreated)
rmdir(dirsCreated{i});
end

end

function existingIncludes = getIncludesFromSysearch...
(fileContent)

targetPrefix = '1'; % look for lines with this prefix
expr = ['^\s*' targetPrefix '\s*([^\n]*)'];
existingIncludes = regexp(fileContent,expr, 'tokens','lineanchors');

for i=1:length(existingIncludes)
existingIncludes{i}=strtrim(existingIncludes{i}{1});
end
end

end

methods (Access=private)

function lSetName = getProposedSetName(this, modifierNumber)

folderName = this.getCodeGenFolder;

if this.isOnTargetMode
% In this case, we will enable special options (e.g. compression) for on-target
% data collection. The results must be kept in a different set
% than that used for a SIL simulation.
name_modifier = '_pil';
else
name_modifier = '';
end

[~, setNameRoot] = fileparts(folderName);
setNameRoot = [setNameRoot name_modifier];

% Checksum for the folder name; this is used to generate a Testbed set name
% based on the current code generation folder name; additionally
% the set name will depend on the modifier number to allow the
% option of a new set name rather than reusing an already
% existing set name that corresponds to the current code
% generation folder.
checksum = CGXE.Utils.md5(folderName, modifierNumber);
checksumStr = sprintf('%6x%6x', checksum(1), checksum(2));
maxLen = namelengthmax;
suffix = ['_' checksumStr];
setNameRootLen = length(setNameRoot);
setNameRoot = setNameRoot(1:min([setNameRootLen maxLen-length(suffix)]));
lSetName = [ setNameRoot suffix ];

end

function lockCleanup = getLock(this)

gotLock = false;
waitSeconds = 0;
dispCount = 0;
waitMaxSeconds = 1000;

while ~gotLock
if isempty(this.LockDir)
% Handle the case where tempdir is configured to be a sub-folder below the main
% temp folder
mainTempFolder = regexprep...
(tempdir, '(^.*(\\|/)(Temp|TEMP|temp|tmp)).*$','$1');
this.LockDir = fullfile(mainTempFolder, 'MathWorks_LDRA_lock_folder');
end

[~, ~, messid] = mkdir(this.LockDir);
if ~strcmp(messid,'MATLAB:MKDIR:DirectoryExists')
gotLock = true;
lockCleanup = onCleanup(@()rmdir(this.LockDir, 's'));
else
delta = 0.2; % seconds to wait
dispSeconds = 10;
waitSeconds = waitSeconds + delta;
if waitSeconds > dispCount * dispSeconds
waitMsg = DAStudio.message('CoderCoverage:LDRA:LDRALockFileExists', ...
this.LockDir);
disp(waitMsg)
dispCount = dispCount+1;
end
if waitSeconds > waitMaxSeconds
rmdir(this.LockDir,'s')
assert(false, ['Timeout waiting for lock to run LDRA command. ' ...
'The lock folder has been deleted. ']);
end
pause(delta);
end
end
end

function isOnTarget = isOnTargetMode(this)

isOnTarget = ...
this.isPilBuild || ~isempty(getenv('LDRA_FORCE_ON_TARGET_MODE'));

end

function lSetName = getSetName(this)

lSetName = coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'read', 'SetName');

if isempty(lSetName)

% If the info file was not found, it may be the entire code generation
% folder was previously deleted. If this folder was used
% previously then previous LDRA analysis results may exist. In
% this case it is preferable to use a new set so that we avoid
% any issues with stale analysis results.
foundEmptySetName = false;
attemptNumber = 1;
while ~foundEmptySetName
lSetName = getProposedSetName(this, attemptNumber);
[~, foundEmptySetName] = getWorkDir(this, lSetName);
attemptNumber = attemptNumber + 1;
end

coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'write', 'SetName', lSetName);

% Ensure that LDRA work area remains consistent with the set name
coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'write', 'WorkDir', '');
this.Workdir = '';
% Ensure that LDRA work area remains consistent with the set name
coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'write', 'CodeGenFolder', ...
this.getCodeGenFolder);

end

end

function checkForConflictingIncludes(this, newIncludes, potentialConflictIncludes)
% If a header with the same name is included from two different folders we must
% error out. For example, the compile may fail if two different
% rtwtypes.h include files have been processed for the same Testbed set.

newHeaders = {};
newHeadersIdx=[];
potentialConflictHeaders = {};
conflictHeadersIdx = [];

for i=1:length(newIncludes)
includeFolder = newIncludes{i};
headers = dir(fullfile(includeFolder, '*.h*'));
n=length(headers);
[newHeaders{end+1:end+n}] = deal(headers(1:end).name);
newHeadersIdx(end+1:end+n)=i;
end

for i=1:length(potentialConflictIncludes)
includeFolder = potentialConflictIncludes{i};
headers = dir(fullfile(includeFolder, '*.h*'));
n=length(headers);
[potentialConflictHeaders{end+1:end+n}] = deal(headers(1:end).name);
conflictHeadersIdx(end+1:end+n)=i;
end

[conflictHeaders, idxNew, idxConflict]...
= intersect(newHeaders, potentialConflictHeaders);

if ~isempty(conflictHeaders)
folder1 = newIncludes{newHeadersIdx(idxNew(1))};
folder2 = potentialConflictIncludes{conflictHeadersIdx...
(idxConflict(1))};
DAStudio.error('CoderCoverage:LDRA:LDRAConflictingHeaders',...
conflictHeaders{1}, this.getCodeGenFolder, ...
fullfile(folder1,conflictHeaders{1}),...
fullfile(folder2,conflictHeaders{1}),...
this.getWorkDirCached, this.getTestbedFolder);
end

end

function [workDir, workDirEmpty] = getWorkDir(this, lSetName)
[~, workareaBaseDir] = coder.coverage.LDRA.getTestbedIniOption...
(this.getTestbedIni, 'WORKAREA_BASEDIR');
workDir = fullfile(workareaBaseDir, [lSetName, '_tbwrkfls']);
files = dir(workDir);
if length(files)>2
% Set folder is non-empty
workDirEmpty = false;
else
workDirEmpty = true;
end
end

function lTestbedFolder = getTestbedFolder(this)
if this.isOnTargetMode
name_modifier = '_pil';
else
name_modifier = '';
end
lTestbedFolder = fullfile(this.getCodeGenFolder, ...
['LDRA_Testbed' name_modifier]);
end

function [fileNamesFromTbset, fileExists] = getFilesWithExistingAnalysis(this, workDir)

% tbset.dat is located in parent folder of workDir
workareaBaseDir = fileparts(workDir);

allSetNames = coder.coverage.TestbedAnalysis...
.getSetNames(workareaBaseDir);

isSetRegistered = any(strcmp(this.SetName, allSetNames));

if isSetRegistered
fileNamesFromTbset = coder.coverage.TestbedAnalysis.getFilesFromSet...
(workareaBaseDir, this.SetName);
else
fileNamesFromTbset = {};
end

fileExists = true(size(fileNamesFromTbset));
if ~isempty(fileNamesFromTbset)
for i=1:length(fileNamesFromTbset)
filename=fileNamesFromTbset{i};
d = dir(filename);
if isempty(d)
fileExists(i) = false;
end
fileNamesFromTbset{i}=filename;
end
end
end

function lBuildOptionsFile = getTestbedBuildOptionsFile(this)

lBuildOptionsFile = fullfile(this.TestbedFolder, 'testbed.dat');

if isempty(dir(lBuildOptionsFile))

% The testbed.dat file uses tokens that expand to the name of the original
% source file and to the original source file extension
origSrcFileName = ['$$o.' this.getInstrPrefix '$$t'];

fileContent = sprintf('* Build options file for Testbed');

option = 'f';
value = origSrcFileName;
fileContent = sprintf('%s\n%s %s',...
fileContent, option, value);

% Write the build options file back out
fid = fopen(lBuildOptionsFile,'w');
fwrite(fid, fileContent);
fclose(fid);
end
end

function lSysearchFile = getSysearchFile(this)

% Standard line type prefixes for the sysearch.dat file
comment = ' 0';
incFile = ' 1';
excludedIncFile = '300';

needToWrite = false;

lSysearchFile = this.SysearchFile;

if ~isempty(dir(lSysearchFile))
% sysearch.dat already exists
fileContent=fileread(lSysearchFile);
existingIncludes = coder.coverage.LDRA.getIncludesFromSysearch...
(fileContent);
else
% Generate the baseline content
fileContent = sprintf('%s File generated by Simulink Coder\n%s\n', ...
comment, comment);

excludedFiles = {};

% Exclude some files that are not used for production builds but are problematic
% for analysis with Testbed. E.g. simstruc.h requires macros
% that are normally passed via the compiler command line.
excludedFiles{end+1} = 'simstruc.h';

fileContent = sprintf(['%s%s\n%s The following include files '...
'are excluded from the analysis\n'],...
fileContent, comment, comment);

for i=1:length(excludedFiles)
fileContent = sprintf('%s%s %s\n',...
fileContent, excludedIncFile, ...
excludedFiles{i});
end

fileContent = sprintf('%s%s', fileContent, comment);

existingIncludes = {};
needToWrite = true;
end

buildInfo = this.getInstrumentedComponentBuildInfo;
replaceMatlabRoot = 1;
newIncludes = buildInfo.getIncludePaths(replaceMatlabRoot);

% Make sure all paths are in canonical form
isAbsPath = coder.coverage.CodeCoverageHook.isAbsPath(newIncludes);
for i=1:length(newIncludes)
newInclude = newIncludes{i};
if ~isAbsPath(i)
% Path is relative to the instrumented folder (where the component
% buildInfo is serialized)
newInclude=fullfile(getInstrumentedBuildFolder(this), newInclude);
end
newInclude = builtin('_canonicalizepath', newInclude);
newIncludes{i} = newInclude;
end
newIncludes = unique(newIncludes);

% filter out include paths that are already in sysearch.data
[~,~,idxNewIntersect] = intersect(existingIncludes, newIncludes);
newIncludes(idxNewIntersect)='';

% Identify potentially conflicting include paths
this.checkForConflictingIncludes(newIncludes, existingIncludes);

if ~isempty(newIncludes)

fileContent = sprintf('%s\n%s Include folders\n', fileContent, comment);

for i=1:length(newIncludes)
fileContent=sprintf('%s%s %s\n', fileContent, incFile, newIncludes{i});
end

fileContent = sprintf('%s\n%s', fileContent, comment);

needToWrite = true;
end

if needToWrite
% Write out the sysearch.dat file
fid = fopen(lSysearchFile,'w');
fwrite(fid, fileContent);
fclose(fid);
end
end

function nextRunName = getNextRunName(this)

lastRunNumberFile = fullfile(this.getInstrumentedBuildFolder, 'last_run_number.txt');

last_run_number_tok = 'last_run_number';

if ~isempty(dir(lastRunNumberFile))
fileContent = fileread(lastRunNumberFile);
expr = ['^' last_run_number_tok '=([^\n]*)'];
valueStr = regexp(fileContent,expr, 'tokens', 'once','lineanchors');
else
valueStr = '0';
end

value = str2double(valueStr);

nextValue = value+1;
nextValueStr = sprintf('%d', nextValue);
[~, buildDirName] = fileparts(this.getInstrumentedBuildFolder);
nextRunName = ['Run ' nextValueStr ' (' buildDirName ')'];

% Write back out to the file
fileContent = sprintf...
('%s\n%s=%s', ...
'Last run number used for code coverage simulation', ...
last_run_number_tok, ...
nextValueStr);
fid = fopen(lastRunNumberFile, 'w');
fwrite(fid, fileContent);
fclose(fid);
end

function lTestbedIni = getTestbedIni(this)
if isempty(this.TestbedIni)
found=false;
testbedEnvVars = {getenv('TESTBED'), getenv('windir')};
for i=1:length(testbedEnvVars)
lTestbedIni = fullfile(testbedEnvVars{i}, 'Testbed.ini');
if ~isempty(dir(lTestbedIni))
found=true;
break;
end
end
if ~found
DAStudio.error('CoderCoverage:LDRA:LDRATestbedIniNotFound');
end
this.TestbedIni=lTestbedIni;
else
lTestbedIni = this.TestbedIni;
end
end

function onCleanupRestoreValue = setTestbedIniOption...
(this, option, value, profile, doingCleanup)

if nargin < 4
profile = '';
end

if ~isempty(profile)
profileOpt = sprintf('%sprofile="C/C++ %s"', this.OptionStartChar, profile);
else
profileOpt = '';
end

if nargin < 5
doingCleanup = false;
end

origOptNotFound = 0;

if ~doingCleanup
[origOptNotFound, originalValue] = coder.coverage.LDRA.getTestbedIniOption...
(this.getTestbedIni, option);
end

cmd = sprintf('%s%s%s %s %s=%s', ...
this.getToolPath, filesep, this.TBiniCmd,...
profileOpt, option, value);

this.systemCommand(cmd);

% Check that the option has been set
if ~origOptNotFound
lTestbedIni = this.getTestbedIni;
[~, readBackValue] = coder.coverage.LDRA.getTestbedIniOption...
(lTestbedIni, option);
if ~strcmp(value, readBackValue)
DAStudio.error('CoderCoverage:LDRA:TestbedIniNotUpdated', ...
option, value, option, readBackValue, lTestbedIni);
end
end

if ~doingCleanup
doingCleanupArgForCallback=true;

onCleanupRestoreValue = onCleanup...
(@()setTestbedIniOption ...
(this, option, originalValue, profile, ...
doingCleanupArgForCallback));
end

end

function hyperlink = getTestbedHyperlink(this)

cmd = sprintf('%s%s%s %s',...
this.getToolPath, ...
filesep, ...
this.TestbedCmd, ...
this.SetName);

isCmdBackground = true;
cmd = this.buildCallCommand(cmd, isCmdBackground);

cmd = sprintf('system(''%s'');', cmd);

hyperlink = targets_hyperlink_manager...
('new',...
DAStudio.message('CoderCoverage:LDRA:LdraTestbedGuiLink'),...
cmd);

end

function cmd = buildCallCommand(~, cmd, isCmdBackground)
% Constructs program call command on Windows or Linux for
% execution in background when isCmdBackground is true.
% Otherwise, execution in foreground.
if ispc
if isCmdBackground
cmdBackground = '';
else
cmdBackground = '/wait';
end
% Calls program execution in windows
cmd = sprintf('start %s %s',cmdBackground, cmd);
else
if isCmdBackground
% To start the program in background
cmdBackground = '&';
else
cmdBackground = '';
end
% Calls program execution in Linux
cmd = sprintf('%s %s', cmd, cmdBackground);
end
end

function [hyperlink, htmlFile] = getHyperLinkForReports(this)

workdir = this.getWorkDirCached;

htmlFile=fullfile(workdir, [this.SetName '.ms3.htm']);

cmd = ['coder.internal.showHtml('...
'''' htmlFile ''', ' ...
'''UseExternalWebBrowser'');'];
hyperlink = targets_hyperlink_manager...
('new',...
DAStudio.message('CoderCoverage:LDRA:LdraTestbedOverviewReportLink'), ...
cmd);
end

function updateWorkdir(this)

% First try to get the cached LDRA work area if it exists
workdir = this.getWorkDirCached;

if isempty(workdir)
[~, codeGenFolderPart] = fileparts(this.getCodeGenFolder);

lTestbedIni = this.getTestbedIni;
[~, workdir] = coder.coverage.LDRA.getTestbedIniOption(lTestbedIni,'WORKDIR');

if strcmp(workdir(end),filesep)
workdir=workdir(1:end-1);
end
[~, subFolder] = fileparts(workdir);

if ~strncmp(this.SetName, subFolder, length(codeGenFolderPart))
DAStudio.error('CoderCoverage:LDRA:LDRADiffSetOpenInGUI', ...
lTestbedIni, workdir, this.SetName);
end

coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'write', 'WorkDir', workdir);
end

this.Workdir = workdir;

end

function workdir = getWorkDirCached(this)

workdir = this.Workdir;

if isempty(this.Workdir)
workdir = coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'read', 'WorkDir');

this.Workdir = workdir;
end
end

function sysppvar = getSysppvarFile(this)

sysppvar = fullfile(this.TestbedFolder,'sysppvar.dat');

% Write out the file if it doesn't already exist
if isempty(dir(sysppvar))

model = this.getModelName;

% Normally the compiler would get MACROS, e.g. SCHAR_MIN, from
% limits.h. However, the LDRA static analysis does not normally
% include system header files so we have to provide values for
% the following MACROS. If these MACROS are missing, there is a
% compiler pre-processor check (see rtwtypes.h) that gives a
% compile error.
bitsPerChar = get_param(model,'TargetBitPerChar');
bitsPerShort = get_param(model, 'TargetBitPerShort');
bitsPerInt = get_param(model, 'TargetBitPerInt');
bitsPerLong = get_param(model, 'TargetBitPerLong');
bitsPerLongLong = get_param(model, 'ProdBitPerLongLong');
signed = true;
unsigned = ~signed;

sCharMin = sprintf('-0x%s', dec2hex(2^(bitsPerChar-1)));

sCharMax = coder.coverage.getIntegerMax(bitsPerChar,signed,'char');
uCharMax = coder.coverage.getIntegerMax(bitsPerChar,unsigned,'char');
uShortMax = coder.coverage.getIntegerMax(bitsPerShort,unsigned,'short');
ShortMax = coder.coverage.getIntegerMax(bitsPerShort,signed,'short');
uIntMax = coder.coverage.getIntegerMax(bitsPerInt,unsigned,'int');
IntMax = coder.coverage.getIntegerMax(bitsPerInt,signed,'int');
uLongMax = coder.coverage.getIntegerMax(bitsPerLong,unsigned,'long');
LongMax = coder.coverage.getIntegerMax(bitsPerLong,signed,'long');

% if model Enables use of C long long data type
if strcmp(get_param(model,'ProdLongLongMode'),'on')
LongLongMax = coder.coverage.getIntegerMax(bitsPerLongLong,signed, 'long long');
uLongLongMax = coder.coverage.getIntegerMax(bitsPerLongLong,unsigned, 'long long');
LongStr = sprintf('%s\n%s\n',...
['LLONG_MAX (' LongLongMax ')'],...
['ULLONG_MAX (' uLongLongMax ')']);
else
LongStr = '';

end

fileContent = sprintf...
('%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n',...
['SCHAR_MIN (' sCharMin ')'],...
['SCHAR_MAX (' sCharMax ')'],...
['UCHAR_MAX (' uCharMax ')'],...
['USHRT_MAX (' uShortMax ')'],...
['SHRT_MAX (' ShortMax ')'],...
['UINT_MAX (' uIntMax ')'],...
['INT_MAX (' IntMax ')'],...
['ULONG_MAX (' uLongMax ')'],...
['LONG_MAX (' LongMax ')']);

% Add long long max integers if model requires it
fileContent = sprintf('%s%s', fileContent, LongStr);

% When tmwtypes.h is included (e.g. if using non-inlined s-functions and
% generally not the case for production code) we need values for
% FLT_MANT_DIG and DBL_MANT_DIG. Normally these are obtained
% from the system header file float.h. To support the static
% analysis, we must supply values for these macros.
fltMantDig = 24;
dblMantDig = 53;

fileContent = sprintf...
('%s\n%s %d\n%s %d\n',...
fileContent, ...
'FLT_MANT_DIG', fltMantDig, ...
'DBL_MANT_DIG', dblMantDig);

if this.isSilBuildAndPortableWordSizes
fileContent = sprintf...
('%s%s\n', ...
fileContent, ...
'PORTABLE_WORDSIZES');
end

fid = fopen(sysppvar,'w');
fwrite(fid, fileContent);
fclose(fid);
end
end

function configureCoverageForThisComponent...
(this, filesToInstrument, sharedUtilIdx, instrumentationUpToDate)

% Configure the sysearch.dat file
lSysearchFile = this.getSysearchFile;
sysearchOpt = sprintf('%ssysearch=%s', ...
this.OptionStartChar, ...
lSysearchFile);

% Configure the sysppvar.dat file
sysppvarFile = this.getSysppvarFile;
sysppvarOpt = sprintf('%ssysppvar=%s',...
this.OptionStartChar, ...
sysppvarFile);

% Configure the instrumentation data file C/CPP_matlab_instr.dat file
targetLang = get_param(this.getModelName, 'TargetLang');

[useMathWorksInstrDatFile, instrFileC, instrFileCpp] = ...
this.getCInstrDatFile(targetLang);

instrFileOpt = sprintf('%sCINSTRFILE="%s" %sCPPINSTRFILE="%s"', ...
this.OptionStartChar, ...
instrFileC, ...
this.OptionStartChar, ...
instrFileCpp);

% Configure the build options file
if ispc
[~, testbedDefaultCompiler] = coder.coverage.LDRA.getTestbedIniOption...
(this.getTestbedIni, 'COMPILER_SELECTED');
else
% On Linux, presently, the Testbed.ini file only stores configuration data for
% a single compiler.
testbedDefaultCompiler = [];
end

onCleanupTestbedIni = this.setBuildOptionsTestbedIni(testbedDefaultCompiler); %#ok<NASGU>

% Set LDRA options if using the MathWorks_C/CPPinstr.DAT file
% otherwise leave these options untouched
if useMathWorksInstrDatFile
% set the INSTR_TEMPLATE_IO option to be TRUE:
onCleanupTestbedIniOptionTemplateIO = ...
this.setTestbedIniOption('INSTR_TEMPLATE_IO', ...
'TRUE', ...
testbedDefaultCompiler); %#ok
% set the EXH_IN_ONE_FILE option to be FALSE:
onCleanupTestbedIniOptionExhInOneFile = ...
this.setTestbedIniOption('EXH_IN_ONE_FILE', ...
'FALSE', ...
testbedDefaultCompiler); %#ok
% set the compression options:
onCleanupTestbedIniOptionCompressExh = ...
this.setTestbedIniOption('COMPRESS_EXH', ...
'TRUE', ...
testbedDefaultCompiler); %#ok
onCleanupTestbedIniOptionMCDCCompress = ...
this.setTestbedIniOption('COMPRESS_EXH_METHOD', ...
'MCDC_BITMAP', ...
testbedDefaultCompiler); %#ok
end

% Identify files with existing analysis results
[workDir, workDirEmpty] = getWorkDir(this, getSetName(this));

if ~workDirEmpty
[previouslyAnalyzedFiles, previousFileExists] = ...
getFilesWithExistingAnalysis(this, workDir);
% If any previously analyzed files no longer exist, perform a new analysis. In
% principle, the files that no longer exist can be individually
% removed from the set; however, this is a complex procedure
% because the file must exist (can be created temporarily) in
% order to be removed.
if ~all(previousFileExists)
filesToAdd = filesToInstrument;
createNewSet = true;
else
filesToAdd = setdiff(filesToInstrument, previouslyAnalyzedFiles);
createNewSet = isempty(previouslyAnalyzedFiles);
end
else
createNewSet = true;
filesToAdd = filesToInstrument;
end

% If creating a new set delete any previously existing files
if createNewSet && ~workDirEmpty
rmdir(workDir, 's')
end

% Main options
% q quit
% 1 static phase menu
% 2 instrumentation phase menu
% 3 dynamic phase menu
%
% Static phase options
% 0 return to main menu
% q quit Testbed
% 1 perform main static analysis
% 2 perform complexity analysis - you then get the flow graphs
%
% Instrumentation options
% 0 return to main menu
% q quit Testbed
% 1 instrument the program
% 2 build the program
%
% Dynamic options
% 0 return to main menu
% q quit Testbed
% 1 execute instrumented program
% 2 perform dynamic coverage analysis

% Create a new set if required
if createNewSet
% A Testbed "Set" can be either a group or a system; for a group, the files are
% analyzed individually
createSetOpt = sprintf('%screate_set=system',this.OptionStartChar);

% For v8.4.1 of Testbed, it's not possible invoke Contestbed to create the set
% because the MCDC coverage option does not get switched on. Invoking
% Testbed instead of Contestbed works around this issue.
cmd1 = sprintf('%s%s%s %s %s %s1q %slog',...
this.getToolPath, ...
filesep, ...
this.TestbedCmd, ...
this.SetName, ...
createSetOpt, ...
this.ParamStartChar, ...
this.OptionStartChar);

isCmdBackground = false;
cmd1 = this.buildCallCommand(cmd1, isCmdBackground);

this.systemCommand(cmd1);

[workDirCreated, workDirEmpty] = getWorkDir(this, getSetName(this));
assert(~workDirEmpty && strcmp(workDirCreated, workDir), ...
'Failed to create new set in expected location');

end

% If files in the set have changed and must re-analyzed, the default command
% line behaviour is that they do not get re-analyzed and there is a
% warning that this would cause dynamic coverage results to be
% deleted. We can force the files to be re-analyzed with the
% following option (and any dynamic coverage results are deleted by Testbed).
% Note that this option is only needed if the set type is "system".
if ~isempty(filesToAdd)

% Without this option Testbed fails to add the files to the
% set and they do not get instrumented. The option
% instructs Testbed that "interdependent" analysis results
% that are invalidated by adding the new files should be
% discarded. In particular, existing code coverage results
% will be discarded.
changedSetOpt = sprintf('%sreanalyse_changed_set',this.OptionStartChar); % g764164
cmd2 = sprintf('%s%s%s %s %s %s1q %slog',...
this.getToolPath, ...
filesep, ...
this.ContestbedCmd,...
this.SetName,...
changedSetOpt, ...
this.ParamStartChar, ...
this.OptionStartChar);

addSetFileOpts = cell(size(filesToAdd));
for k = 1:length(filesToAdd)
fileName = filesToAdd{k};
addSetFileOpt=sprintf('%sadd_set_file="%s" %s1q', ...
this.OptionStartChar, fileName, this.ParamStartChar);
addSetFileOpts{k} = addSetFileOpt;
end

% To avoid exceeding the maximum command line length this command may need to be
% split into smaller sub-commands
this.systemCommandWithSeparableArgs(cmd2, addSetFileOpts);
end

% Option that only forces re-analysis for phases that are
% not already up to date
batchModeOpt = sprintf('%sbatch_mode_verify_results',this.OptionStartChar);

% Now run the main analysis, complexity analysis and instrumentation command
cmd3 = sprintf('%s%s%s %s %s %s %s112021q %s %s %slog',...
this.getToolPath, ...
filesep, ...
this.ContestbedCmd,...
this.SetName, ...
sysppvarOpt, ...
instrFileOpt, ...
this.ParamStartChar , ...
batchModeOpt, ...
sysearchOpt, ...
this.OptionStartChar);

[~, lockCleanup] = this.systemCommand(cmd3); %#ok

% Update work area name before releasing the lock
this.updateWorkdir;
clear lockCleanup;

% Now the set is created we can restore the TESTBED.INI option
clear onCleanupTestbedIni;

% Update checksums now that instrumentation is done
coder.coverage.setAllInstrumentationUpToDate...
(filesToInstrument, sharedUtilIdx, instrumentationUpToDate, ...
getInstrPrefix(this), getOriginalBuildFolder(this));

end

function onCleanupTestbedIni = setBuildOptionsTestbedIni(this,...
testbedDefaultCompiler)
lBuildOptionsFile = this.getTestbedBuildOptionsFile;
onCleanupTestbedIni.BuildOption = this.setTestbedIniOption...
('BUILD_OPTIONS_FILE', ...
lBuildOptionsFile, testbedDefaultCompiler);

% Adding support for the CPP_BUILD_OPTIONS_FILE option if present in
% the Testbed.ini file. We use the same value as
% BUILD_OPTIONS_FILE for consistency
[cppBuildOptionFileNotFound, ~] = ...
this.getTestbedIniOption(this.getTestbedIni,...
'CPP_BUILD_OPTIONS_FILE');
if ~cppBuildOptionFileNotFound
onCleanupTestbedIni.CPPBuildOption = ...
this.setTestbedIniOption('CPP_BUILD_OPTIONS_FILE', ...
lBuildOptionsFile, testbedDefaultCompiler);
end

% Adding support for the C_BUILD_OPTIONS_FILE option if present in
% the Testbed.ini file. We use the same value as
% BUILD_OPTIONS_FILE for consistency
[cBuildOptionFileNotFound, ~] = ...
this.getTestbedIniOption(this.getTestbedIni,...
'C_BUILD_OPTIONS_FILE');
if ~cBuildOptionFileNotFound
onCleanupTestbedIni.CBuildOption = ...
this.setTestbedIniOption('C_BUILD_OPTIONS_FILE', ...
lBuildOptionsFile, testbedDefaultCompiler);
end
end

end

methods (Access=public, Static=true)

function setPath(ldraPath)
ldraObj = coder.coverage.LDRA;
ldraObj.setToolPath(ldraPath);
end

function ldraPath = getPath
ldraObj = coder.coverage.LDRA;
try
ldraPath = ldraObj.getToolPath;
catch e
if strcmp(e.identifier,'CoderCoverage:AllTools:toolPathNotSet')
ldraPath = '';
else
rethrow(e);
end
end
end

function qqqbranches = getNumQqqbranches(instrSrc)

instrContent = fileread(instrSrc);

qqqbranchline = regexp(instrContent, ...
'#define\s+qqqbranches\s+[0-9]+', 'match', 'once');

if isempty(qqqbranchline)
% For example, a source file with only comments has no coverage to measure
qqqbranches = 0;
else
qqqbranches = str2double(regexp(qqqbranchline, '[0-9]+$',...
'match'));
end
end

function valueOut = ldraInfoFile(lTestbedFolder, cmd, valueName, valueIn)

ldraInfoFile = fullfile(lTestbedFolder, 'info.mat');
if ~isempty(dir(ldraInfoFile))
info = load(ldraInfoFile);
info = info.info;
else
info = struct('WorkDir','', ...
'SetName','', ...
'VersionId',coder.coverage.LDRA.VersionId, ...
'CodeGenFolder','');
end

if strcmp(cmd, 'read')
valueOut = info.(valueName);
else
info.(valueName) = valueIn;
save(ldraInfoFile, 'info');
end

end

function [optionNotFound, value] = getTestbedIniOption(lTestbedIni, optionName)

fileContent = fileread(lTestbedIni);
optionNotFound = 0;

% Depending on the option, we need to identify the correct section of the file
% to search

% The following line needs to be made up of '<lang>' and ' LDRA Testbed'
c_cpp_ldra_testbed_expr = '[C/C++ LDRA Testbed]';
idx = strfind(fileContent, c_cpp_ldra_testbed_expr);
c_cpp_ldra_testbed_section = fileContent(idx:end);

if ispc
% Identify the COMPILER_SELECTED option only under Windows
% because Testbed.ini is not structured with Compiler
% sections under Linux
compiler_selected_expr = '^COMPILER_SELECTED=([^\n]*)';
compiler_selected = regexp...
(c_cpp_ldra_testbed_section, compiler_selected_expr, ...
'tokens', 'once', 'lineanchors');
assert(~isempty(compiler_selected),...
'The setting COMPILER_SELECTED could not be found in %s\n',...
optionName, lTestbedIni);
compiler_selected = strtrim(compiler_selected{1});

% Identify section of the file identified by '<lang>',
% 'COMPILER_SELECTED' and ' LDRA Testbed', e.g.
% [C/C++ Microsoft Visual C/C++ v6.0 LDRA Testbed]
compiler_section_expr = ['[C/C++ ' compiler_selected ...
' LDRA Testbed]'];
idx = strfind(fileContent, compiler_section_expr);
compiler_section=fileContent(idx:end);
end

if strcmp(optionName, 'BUILD_OPTIONS_FILE') && ispc
searchRegion = compiler_section;
elseif isunix
% if on Linux, search through the whole file
searchRegion = fileContent;
else
% if is PC and not BUILD_OPTIONS_FILE
searchRegion = c_cpp_ldra_testbed_section;
end

expr = ['^' optionName '=([^\n]*)'];
value = regexp(searchRegion, expr, 'tokens', 'once','lineanchors');
if isempty(value)
value = '';
optionNotFound = 1;
else
value=strtrim(value{1});
end
end
end

methods (Access=public)

function [result, lockCleanup] = systemCommand(this, varargin)

lockCleanup = this.getLock;

result = systemCommand@coder.coverage.CodeCoverageHook(this, varargin{:});
end

function result = systemCommandWithSeparableArgs(this, varargin)

lockCleanup = this.getLock; %#ok

result = systemCommandWithSeparableArgs...
@coder.coverage.CodeCoverageHook(this, varargin{:});
end

function [updatedCInstrDatFile, updatedCppInstrDatFile] = ...
updateCinstrDatFiles...
(this, mwCInstrDatFile, mwCppInstrDatFile)

lTestbedFolder = this.getTestbedFolder;
expr = 'uint16_T(\s*targetFid)';
replacement = 'unsigned short$1';

inputFiles = {mwCInstrDatFile, mwCppInstrDatFile};
outputFiles = cell(size(inputFiles));

for i=1:length(inputFiles)
inFile = inputFiles{i};
[~, f, e] = fileparts(inFile);
outFile = fullfile(lTestbedFolder, [f e]);
if isempty(dir(outFile))
fileContent = fileread(inFile);
fileContent = regexprep(fileContent, ...
expr, replacement, 'once');
fid = fopen(outFile, 'w');
fwrite(fid, fileContent);
fclose(fid);
end
outputFiles{i} = outFile;
end
updatedCInstrDatFile = outputFiles{1};
updatedCppInstrDatFile = outputFiles{2};
end

function fileNamesActualCase = caseInsensitiveDir...
(~, folder, fileNameAnyCase)

% First find all files in specified folder
files = dir(folder);
N = length(files);
[fileNames{1:N}] = files(:).name;

% Find matching files
matchIdx = strcmpi(fileNames, fileNameAnyCase);
fileNamesActualCase = fileNames(matchIdx);

end

function [mwCInstrDatFileFull, mwCppInstrDatFileFull] = findMwCinstrDatFiles(this)

mwCInstrDatFileFull = '';
mwCppInstrDatFileFull = '';

% The case of the Compiler_spec folder may be platform dependent
compilerSpecFolder = 'Compiler_Spec';
compilerSpecFolderActual = caseInsensitiveDir...
(this, this.getToolPath, compilerSpecFolder);
assert(length(compilerSpecFolderActual)==1, ...
'Compiler_spec folder must exist in %s', this.getToolPath);
compilerSpecFolderFull = fullfile(this.getToolPath, compilerSpecFolderActual{1});

% Find the MathWorks sub-folder
mwFolder = 'MathWorks';
mwFolderActual = caseInsensitiveDir...
(this, compilerSpecFolderFull, mwFolder);
if isempty(mwFolderActual)
% MathWorks folder not found
return;
end
assert(length(mwFolderActual)==1, ...
'Must be no more than one case insensitive match for folder');
mwFolderActual = fullfile(compilerSpecFolderFull, mwFolderActual{1});

% Find the CInstr files
mwCInstrDatFile = 'MathWorks_Cinstr.DAT';
mwCppInstrDatFile = 'MathWorks_CPPinstr.DAT';
mwCInstrDatFileFull = caseInsensitiveDir...
(this, mwFolderActual, mwCInstrDatFile);
mwCppInstrDatFileFull = caseInsensitiveDir...
(this, mwFolderActual, mwCppInstrDatFile);
assert(length(mwCInstrDatFileFull)==1, ...
'Must be no more than one case insensitive match for %s', mwCInstrDatFile);
assert(length(mwCppInstrDatFileFull)==1, ...
'Must be no more than one case insensitive match for %s', mwCppInstrDatFile);
if isempty(mwCInstrDatFileFull)
mwCInstrDatFileFull = '';
else
mwCInstrDatFileFull = fullfile(mwFolderActual, mwCInstrDatFileFull{1});
end
if isempty(mwCppInstrDatFileFull)
mwCppInstrDatFileFull = '';
else
mwCppInstrDatFileFull = fullfile(mwFolderActual,mwCppInstrDatFileFull{1});
end
end

function [defaultCInstrDatFile, defaultCppInstrDatFile] = ...
getDefaultCInstrDatFiles(this)

if ispc
% Looks for these filenames if current platform is Windows PC
defaultCInstrDatFile = fullfile(this.getToolPath, 'C', ...
'Cinstr.DAT');
defaultCppInstrDatFile = fullfile(this.getToolPath, 'Cpp', ...
'CPPINSTR.DAT');
else
% Looks for these filenames if current platform is Linux
defaultCInstrDatFile = fullfile(this.getToolPath, 'c', ...
'cinstr.dat');
defaultCppInstrDatFile = fullfile(this.getToolPath, 'cpp', ...
'cppinstr.dat');
end

end

function [useMathWorksInstrDat, cInstrDatFile, cppInstrDatFile] = ...
getCInstrDatFile(this, targetLang)

useMathWorksInstrDat = false;
mwCinstrMissing = false;

if this.isOnTargetMode
[mwCInstrDatFile, mwCppInstrDatFile] = findMwCinstrDatFiles(this);
if isempty(dir(mwCInstrDatFile)) || isempty(dir(mwCppInstrDatFile))
mwCinstrMissing=true;
else
useMathWorksInstrDat = true;
end
end

if useMathWorksInstrDat
[cInstrDatFile, cppInstrDatFile] = this.updateCinstrDatFiles...
(mwCInstrDatFile, mwCppInstrDatFile);
else
[cInstrDatFile, cppInstrDatFile] = getDefaultCInstrDatFiles(this);
end

assert(~isempty(dir(cInstrDatFile)),...
'Cannot find %s file, check LDRA installation',...
cInstrDatFile);

assert(~isempty(dir(cppInstrDatFile)),...
'Can not find %s file, check LDRA installation',...
cppInstrDatFile);

switch targetLang
case 'C'
primaryInstrFileFull = cInstrDatFile;
case {'C++', 'C++ (Encapsulated)'}
primaryInstrFileFull = cppInstrDatFile;
otherwise
assert(false, 'SIL/PIL and LDRA code coverage, unsupported setting for TargetLang: %s',...
targetLang);
end

this.dispInfo(sprintf('### %s', DAStudio.message('CoderCoverage:LDRA:LdraBuildLog')));
if mwCinstrMissing
this.dispInfo(sprintf(...
['### %s\n', ...
' \n' ...
' %s\n' ...
' %s\n' ...
' \n' ...
' %s\n\n'...
' %s\n\n'...
' %s'], ...
DAStudio.message('CoderCoverage:LDRA:LdraOnTargetInstrFilesMissing'), ...
mwCInstrDatFile, mwCppInstrDatFile, ...
DAStudio.message('CoderCoverage:LDRA:LdraRevertToDefaultInstr'), ...
primaryInstrFileFull,...
DAStudio.message('CoderCoverage:LDRA:LdraRevertToDefaultImpact')));
else
this.dispInfo(sprintf(...
'### %s %s',...
DAStudio.message('CoderCoverage:LDRA:LdraInstrFile'), ...
primaryInstrFileFull));
end
end

function writeAnnotationXml(this)

[componentHtmlFolders, allSrcFiles] = this.getHtmlFolders;

lTestbedAnalysis = coder.coverage.TestbedAnalysis...
(this.getWorkDirCached, this.SetName);

annotations = coder.coverage.TestbedAnnotations...
(lTestbedAnalysis, this.getInstrPrefix);

this.writeCoverageXml...
(componentHtmlFolders, allSrcFiles, annotations);
end

function this = LDRA(varargin)

this.processConstructorArgs(varargin{:});

% Specify a file that can be used to validate the LDRA installation
% including path relative to the installation root folder.
% Set a different executable name depending whether we are
% running on Windows or Linux
if ispc
markerFile='Contestbed.exe';
this.setValidateToolInstallationFile(markerFile);
this.ContestbedCmd = [markerFile, ' /invoked_from=Simulink' ];
this.OptionStartChar = '/';
this.ParamStartChar = '/';
this.TBiniCmd = 'TBini.exe';
this.TestbedCmd = 'Testbed.exe';
this.ToolsuiteFile = 'Toolsuite.ctl';
else
markerFile='contestbed';
this.setValidateToolInstallationFile(markerFile);
this.ContestbedCmd = [markerFile, ' -invoked_from=Simulink' ];
this.OptionStartChar = '-';
this.ParamStartChar = '';
this.TBiniCmd = 'tbini';
this.TestbedCmd = 'testbed';
this.ToolsuiteFile = 'toolsuite.ctl';
end

% Specify the code coverage tool name (used as a key for preferences such as the
% installation path that must persist across sessions).
this.setToolNameForPreferences('LDRA');

end

function version = getActualVersion(this)

ctlFile = fullfile(this.getToolPath, this.ToolsuiteFile);
version = '';

if ~isempty(dir(ctlFile))
fileContent = fileread(ctlFile);
expr = '^\s*VERSION\s*:\s*([^\n]*)';
valueStr = regexp(fileContent,expr, 'tokens', 'once','lineanchors');
if iscell(valueStr) && length(valueStr)==1
version = strtrim(valueStr{1});
end
end

if isempty(version)
% Unknown version: call superclass method to get default string
version = getActualVersion@coder.coverage.CodeCoverageHook(this);
end

end

function entry(this)

if any(strfind(this.getCodeGenFolder,' '))
DAStudio.error('CoderCoverage:AllTools:spacesInPwd', ...
this.getModelName,...
this.getCodeGenFolder)
end

if strncmp(pwd, '\\', 2)
DAStudio.error('CoderCoverage:LDRA:LDRAUNCNotSupported');
end

if this.isOnTargetMode
this.setInstrPrefix('ip_');
else
this.setInstrPrefix('i_');
end

lTestbedFolder = this.getTestbedFolder;
this.TestbedFolder = lTestbedFolder;

% First check on the info file is to look at the version identifier (g775033)
cachedVersionId = coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'read', 'VersionId');
if exist(lTestbedFolder,'dir')==7 && ...
cachedVersionId ~= this.VersionId
% Remove content of the LDRA folder
rmdir(lTestbedFolder,'s');
end

% If the windows drive letter is lower case, it will be transformed to upper
% case during rtwbuild. We need a function to normalize the drive letter
% when comparing build folder names obtained during or outside the
% context of rtwbuild.
normalizeDriveLetter = @(x)regexprep(x,'(^[a-z]:\\)', '${upper($1)}');

cachedCodeGenFolder = coder.coverage.LDRA.ldraInfoFile...
(this.getTestbedFolder, 'read', 'CodeGenFolder');
if exist(lTestbedFolder,'dir')==7 && ...
~strcmp(normalizeDriveLetter(this.getCodeGenFolder), ...
normalizeDriveLetter(cachedCodeGenFolder))
% Remove content of the LDRA folder
rmdir(lTestbedFolder,'s');
end

if ~(exist(lTestbedFolder,'dir')==7)
[s, msg, msgid] = mkdir(lTestbedFolder);
assert(s, 'Failed to create folder for LDRA files: %s, %s', ...
msg, msgid);
end

this.SetName = this.getSetName;

this.SysearchFile = fullfile(lTestbedFolder, 'sysearch.dat');
end

function after_code_generation(this)

filterReadOnlyFolders = true;

if getCoverageEnabledForThisComponent(this)
[filesToInstrument, instrumentationUpToDate, sharedUtilIdx] = ...
coder.coverage.getAllFilesToInstrument...
(getInstrumentedComponentBuildInfo(this), ...
filterReadOnlyFolders, this.BuildSharedUtils, ...
getOriginalBuildFolder(this), ...
getInstrPrefix(this), ...
this.InstrumentedObjectCodeFolder);
else
filesToInstrument = {};
sharedUtilIdx = false(0,0);
instrumentationUpToDate = false(0,0);
end

% Even if the source code is up-to-date we may be compiling
% the object code in different folders for SIL and PIL.
% A different LDRA set name is used corresponding to the different
% object code folders and the analysis needs to be done
% separately for each object code folder
if this.IsNewGeneratedCode || ...
~all(instrumentationUpToDate) || exist(this.getWorkDirCached, 'dir')~=7
this.configureCoverageForThisComponent...
(filesToInstrument, sharedUtilIdx, instrumentationUpToDate);
end

if this.isOnTargetMode
% Get buildInfo and add path of "target_io.h", so instrumented
% source can include it
buildInfo = this.getInstrumentedComponentBuildInfo;
targetIoIncPath = fullfile...
(matlabroot, 'toolbox','rtw','targets','pil','c');
addIncludePaths(buildInfo, targetIoIncPath);
i_addIncludePathsToSharedLibs(buildInfo, targetIoIncPath);
end

% Substitute instrumented source for the original source files
this.substInstrumentedSrcFiles(filesToInstrument);

end

function before_buildApplication(this, targetApplicationBuildInfo)
% Called at the start of the rtw.pil.ApplicationBuildManager
% build method. Allows updates to the target application build
% info.
%
if this.isOnTargetMode
filePath = fullfile(matlabroot, ...
'toolbox', ...
'rtw', ...
'targets', ...
'pil', ...
'c');
targetApplicationBuildInfo.addDefines('LDRA_ENABLED=1', 'OPTS');
targetApplicationBuildInfo.addIncludeFiles('code_coverage_utils.h', filePath);
targetApplicationBuildInfo.addSourceFiles('code_coverage_utils.c', filePath);
% targetFopen & targetFprintf are required
coder.connectivity.PILApplicationDependencies.addTargetIoDependenciesToBuildInfo(targetApplicationBuildInfo);
% set the maximum number of source modules that
% code_coverage_utils.c must support
numSrcsWithCoverage = this.getNumSrcsWithCoverage();
defineStr = sprintf('COV_MAX_SOURCE_MODULES=%d',...
numSrcsWithCoverage);
targetApplicationBuildInfo.addDefines(defineStr, 'OPTS');
end
end

function exit(this)
% Swap back the non-instrumented source code in place of the
% instrumented source.
%
% Previously this was done in the "after_make" hook but we now do
% it in the "exit" hook so that IDE Links use the instrumented
% source when their build process runs from the exit hook.
%
% The following code generation report scenarios were considered:
%
% 1) make_rtw: new code
%
% The report may be generated prior to the call to rtw_c.
% In this case, the report is generated before the
% non-instrumented source has been replaced.
%
% Next, rtw_c is called which triggers the
% "after_code_generation" hook which replaces the
% non-instrumented source with the instrumented source. Note
% that there is a try / catch around rtw_c which will attempt
% to generate the report on failure. In this case, the report
% may be constructed using the instrumented source code!
%
% Next, the "exit" hook is called. The IDE Link exit hook is
% executed before this build hook exit and therefore the IDE
% Link will always see the instrumented source code. This
% hook is then called to swap back the source code.
%
% At this point, report processing may occur; the
% non-instrumented source code will be used.
%
% 2) make_rtw: up to date code
%
% In this case, report processing is done right at the end,
% after the instrumented source code has been replaced by this
% exit hook.

this.doRestoreSourceFilesCleanup;
end

function numSrcsWithCoverage = getNumSrcsWithCoverage(this)

lSetName = getSetName(this);

sourceFilesInSet = coder.coverage.TestbedAnalysis...
.getFilesFromSet(fullfile(getWorkDir(this, lSetName), '..'), lSetName);

numSrcsWithCoverage = length(sourceFilesInSet);

end

function before_on_target_execution(this)
% We need to remove any .exh files for the current set to
% ensure that we do not re-analyze stale data

exhList = dir('*.exh');
if ~isempty(exhList)
[exhFiles{1:length(exhList)}] = deal(exhList.name);
ndxList = dir(fullfile(this.getWorkDirCached, '*.ndx'));
[ndxFiles{1:length(ndxList)}] = deal(ndxList.name);
expectedExh = strrep(ndxFiles, '.ndx', '.exh');
exhFilesToDelete = intersect(exhFiles, expectedExh);
if ~isempty(exhFilesToDelete)
delete(exhFilesToDelete{:})
end
end
end

function after_on_target_execution(this)

datasetName = this.getNextRunName;

this.dispInfo(sprintf('### %s', ...
DAStudio.message('CoderCoverage:LDRA:LdraStartAnalysis')));

% The .exh files are generated in the current folder as this is
% the folder from which the PIL or SIL application is run.
lExhDir = pwd;

cmd = sprintf('%s%s%s %s %s32panq', ...
this.getToolPath, ...
filesep, ...
this.ContestbedCmd,...
this.SetName,...
this.ParamStartChar);

cmd = sprintf('%s %sdataset="%s" %sforce_exhdir="%s" %slog',...
cmd, ...
this.OptionStartChar,...
datasetName,...
this.OptionStartChar,...
lExhDir,...
this.OptionStartChar);

[~, htmlFile] = this.getHyperLinkForReports;

if ~isempty(dir(htmlFile))
% Delete the file so that we can test the call to Testbed
% completed successfully
delete(htmlFile);
end

result = this.systemCommand(cmd);

[hyperlinkLdraHtml, htmlFile] = this.getHyperLinkForReports;

if isempty(dir(htmlFile))
if strcmp(this.getVerbose, 'off')
% The command was not already displayed so we print now to provide diagnostic
% information
lDispHook = this.getDispHook;
feval(lDispHook, cmd);
feval(lDispHook, result);
end

% The analysis may need to be rerun so we clear the checksum (error message
% will explain that user must re-run the simulation)
filesToInstrumentNone = {};
sharedUtilIdx = [];
instrumentationUpToDate = [];
coder.coverage.setAllInstrumentationUpToDate...
(filesToInstrumentNone, sharedUtilIdx, instrumentationUpToDate, ...
getInstrPrefix(this), getOriginalBuildFolder(this));

% Report was not created
DAStudio.error('CoderCoverage:LDRA:LDRAReportNotUpdated');
end

hyperlinkGUI = this.getTestbedHyperlink;

this.writeAnnotationXml;

% HTML code generation report with annotations
[rptFullPath, rptOpenCmd] = this.getHtmlReportFullPath;
if ~isempty(rptFullPath)

% Display hyperlinks based web browser type
hyperlinkHtmlReport = targets_hyperlink_manager ...
('new', ...
DAStudio.message('CoderCoverage:AllTools:HtmlReportWithAnnotationsLink'), ...
rptOpenCmd);
else
hyperlinkHtmlReport = DAStudio.message...
('CoderCoverage:AllTools:noRtwReportForCoverage');
end

this.dispInfo(sprintf('### %s',...
DAStudio.message('CoderCoverage:AllTools:codeCoverageResultLinks')));
this.dispInfo(sprintf(' %s', hyperlinkGUI));
this.dispInfo(sprintf(' %s', hyperlinkLdraHtml));
this.dispInfo(sprintf(' %s', hyperlinkHtmlReport));
this.dispInfo(sprintf('### %s', DAStudio.message('CoderCoverage:AllTools:CompletedCoverage')));

end
end

methods (Access=protected)
function processSystemCommandResult(~, result)
% Inside this hook we detect if the result of the system
% command that calls LDRA includes a license initialization
% failure. We detect that by looking for 'FLEXlm Code -X'.
flexLmErrorCode = regexp(result, 'FLEXlm\sCode\s(-\d+)', 'tokens', 'once');
if ~isempty(flexLmErrorCode)
error(message('CoderCoverage:LDRA:LDRALicenseInitializationFailure', ...
flexLmErrorCode{:}, result));
end
end
end

methods (Static)
function name = getName()
name = 'LDRA';
end
function name = getDisplayName()
name = message('CoderCoverage:LDRA:LDRA').getString;
end
end
end

% Add include path to shared libraries referenced by the buildInfo
function i_addIncludePathsToSharedLibs(buildInfo, incPath)
sharedLibs = buildInfo.LinkObj;
if isempty(sharedLibs)
return;
end
hasBuildInfoIdx = ~cellfun(@isempty, {sharedLibs.BuildInfoHandle});
sharedLibs = sharedLibs(hasBuildInfoIdx);
for i=1:length(sharedLibs)
addIncludePaths(sharedLibs(i).BuildInfoHandle, incPath);
sharedLibs(i).BuildInfoDirty = true;
end
end