classdef tBuildExecutable < matlab.unittest.TestCase
%tBuildExecutable builds an executable from a C source file and executes it.
% The test verifies the existence of the executable and the correct result
% after the execution.
% If the toolchain or template makefile defined as buildMethod in the base
% workspace contains a C++ compiler, the same process is done for a C++ source file.

% Copyright 2020 The MathWorks, Inc.

properties
% Toolchain info object created from the buildMethod variable
% defined in base workspace
TcInfo;
end

properties(ClassSetupParameter)
% Get the build method specified by the user in the base workspace
buildMethod = {getBuildMethod};
end

methods(TestClassSetup)
function setup(test, buildMethod)
% Get toolchainInfo object from buildMethod
test.TcInfo = coder.make.internal.resolveToolchainOrTMF(buildMethod);
test.assumeNotEmpty(test.TcInfo, 'Build method must have an associated ToolchainInfo');
end
end

methods(TestMethodSetup)
function setupWorkingFolder(test)
applyFixture(test, matlab.unittest.fixtures.WorkingFolderFixture);
end
end

methods(Test)

% Build an executable from a C source file
function buildCExecutable(test, buildMethod)

% Create source file to compile
prodName = 'foo';
srcFile = [prodName '.c'];
fid = fopen(srcFile, 'w');
fwrite(fid, ['#include <stdio.h>' newline 'int foo() { volatile int a = 0; } int main() {printf("Hello World");}']);
fclose(fid);

% Create the buildInfo object
lBuildInfo = i_createBuildInfo(prodName,srcFile);

% Do the build
codebuild(lBuildInfo, ...
'BuildMethod', buildMethod, ...
'BuildVariant', 'STANDALONE_EXECUTABLE');

% Verify that final products were created
verifyFinalProduct(test, prodName);
end

% If buildMethod has a C++ compiler, build an executable from a
% cpp file
function buildCPPExecutable(test, buildMethod)

% Determine if BuildMethod supports C++ compilation and filter
% the test out if not
test.assumeTrue(( contains(buildMethod,'.tmf') || ~isempty(test.TcInfo.BuildTools.getValue('C++ Compiler')) ) && ...
~contains(buildMethod,'lcc'),...
'Skipping C++ compilation as it is not supported by buildMethod.');

% Create source file to compile
prodName = 'bar';
srcFile = [prodName '.cpp'];
fid = fopen(srcFile, 'w');
fwrite(fid, ['#include <iostream>' newline 'int bar() { volatile int a = 0; return a++;} int main() {std::cout << "Hello World";return 0;}']);
fclose(fid);

% Create the buildInfo object
lBuildInfo = i_createBuildInfo(prodName,srcFile);

% Do the build
codebuild(lBuildInfo, ...
'BuildMethod', buildMethod, ...
'BuildVariant', 'STANDALONE_EXECUTABLE');
% Verify that final products were created
verifyFinalProduct(test, prodName);
end
end

methods(Access=private)

function verifyFinalProduct(test, prodName)
import matlab.unittest.constraints.IsFile;

% Get executable extension

exeExt = test.TcInfo.BuildTools.getValue('Linker').getFileExtension('Executable');

expectedExeProduct = fullfile([prodName exeExt]);

test.verifyThat(expectedExeProduct,IsFile, 'Verify that the expected file exists.');

% Run the generated executable
if ispc
[status, result] = system(expectedExeProduct);
else
[status, result] = system(['./' expectedExeProduct]);
end

% Verify the file is executed without error and that the result
% is "Hello World"
verifyEqual(test, status, 0, 'Verify the file is executed without error.');
verifyEqual(test,result,'Hello World', 'Verify the executable prints the expected text.');
end
end
end

function lBuildInfo = i_createBuildInfo(prodName,srcFile)
lBuildInfo = RTW.BuildInfo;
lBuildInfo.ComponentName = prodName;
addSourceFiles(lBuildInfo, srcFile, pwd);
end