Skip to content

Commit 91704f0

Browse files
committed
subprocess_run: test with actual executables built on system
1 parent c3434a1 commit 91704f0

File tree

7 files changed

+142
-20
lines changed

7 files changed

+142
-20
lines changed

+stdlib/subprocess_run.m

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@
5757
assert(isfolder(opt.cwd), "directory %s does not exist", opt.cwd)
5858
proc.directory(java.io.File(opt.cwd));
5959
end
60+
%% Gfortran streams
61+
% https://www.mathworks.com/matlabcentral/answers/91919-why-does-the-output-of-my-fortran-script-not-show-up-in-the-matlab-command-window-when-i-execute-it#answer_101270
62+
% Matlab grabs the stdout, stderr, stdin handles of a Gfortran program, even when it's using Java.
63+
% We must disable this behavior for the duration the running process.
64+
65+
outold = getenv("GFORTRAN_STDOUT_UNIT");
66+
setenv("GFORTRAN_STDOUT_UNIT", "6");
67+
errold = getenv("GFORTRAN_STDERR_UNIT");
68+
setenv("GFORTRAN_STDERR_UNIT", "0");
69+
inold = getenv("GFORTRAN_STDIN_UNIT");
70+
setenv("GFORTRAN_STDIN_UNIT", "5");
6071
%% start process
6172
% https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ProcessBuilder.html#start()
6273
h = proc.start();
@@ -69,6 +80,10 @@
6980
writer.close()
7081
end
7182

83+
%% read stdout, stderr pipes
84+
stdout = read_stream(h.getInputStream());
85+
stderr = read_stream(h.getErrorStream());
86+
7287
%% wait for process to complete
7388
% https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/Process.html#waitFor()
7489

@@ -88,13 +103,13 @@
88103
status = h.waitFor();
89104
end
90105

91-
%% read stdout, stderr pipes
92-
stdout = read_stream(h.getInputStream());
93-
stderr = read_stream(h.getErrorStream());
94-
95-
%% close process
106+
%% close process and restore Gfortran streams
96107
h.destroy()
97108

109+
setenv("GFORTRAN_STDOUT_UNIT", outold);
110+
setenv("GFORTRAN_STDERR_UNIT", errold);
111+
setenv("GFORTRAN_STDIN_UNIT", inold);
112+
98113
stderr = tmsg + stderr;
99114

100115
if nargout < 2 && strlength(stdout) > 0
@@ -110,9 +125,14 @@
110125
function msg = read_stream(stream)
111126

112127
% https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/BufferedReader.html
128+
129+
msg = "";
130+
131+
% don't check stream.available() as it may arbitrarily return 0
132+
113133
reader = java.io.BufferedReader(java.io.InputStreamReader(stream));
134+
114135
line = reader.readLine();
115-
msg = "";
116136
while ~isempty(line)
117137
msg = append(msg, string(line), newline);
118138
line = reader.readLine();

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,8 @@ jobs:
4646
if: ${{ matrix.os == 'ubuntu-22.04' }}
4747
run: echo "CXXMEX=g++-10" >> $GITHUB_ENV
4848

49+
- name: Fortran FC
50+
if: runner.os == 'macOS'
51+
run: echo "FC=gfortran-14" >> $GITHUB_ENV
52+
4953
- uses: ./.github/workflows/composite-buildtool

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Filesystem.class
22
CodeIssues.sarif
3-
3+
TestResults.xml
44
code-coverage.xml
55

66
.buildtool/
@@ -9,3 +9,6 @@ docs/
99

1010
*.mex*
1111
*.oct
12+
13+
test/printer_c.exe
14+
test/printer_fortran.exe

buildfile.m

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,22 @@
1111

1212
if isMATLABReleaseOlderThan("R2023b")
1313
plan("test") = matlab.buildtool.Task(Actions=@legacy_test);
14-
else
14+
elseif isMATLABReleaseOlderThan("R2024a")
1515
plan("test") = matlab.buildtool.tasks.TestTask("test", Strict=false);
16+
else
1617
% can't use SourceFiles= if "mex" Task was run, even if plan("test").DisableIncremental = true;
1718
% this means incremental tests can't be used with MEX files (as of R2024b)
19+
plan("test") = matlab.buildtool.tasks.TestTask("test", Strict=false, TestResults="TestResults.xml");
20+
end
1821

22+
if ~isMATLABReleaseOlderThan("R2023b")
1923
plan("clean") = matlab.buildtool.tasks.CleanTask;
2024
end
2125

26+
plan("build_c") = matlab.buildtool.Task(Actions=@subprocess_build_c);
27+
plan("build_fortran") = matlab.buildtool.Task(Actions=@subprocess_build_fortran);
28+
plan("test").Dependencies = ["build_c", "build_fortran"];
29+
2230
if ~isMATLABReleaseOlderThan("R2024a")
2331
plan("check") = matlab.buildtool.tasks.CodeIssuesTask(pkg_name, IncludeSubfolders=true, ...
2432
WarningThreshold=0, Results="CodeIssues.sarif");
@@ -68,7 +76,7 @@ function legacy_mex(context, compiler_opt, linker_opt)
6876

6977

7078
function legacy_test(context)
71-
r = runtests(fullfile(context.Plan.RootFolder, "test"), Strict=false);
79+
r = runtests(context.Plan.RootFolder + "/test", Strict=false);
7280
% Parallel Computing Toolbox takes more time to startup than is worth it for this task
7381

7482
assert(~isempty(r), "No tests were run")
@@ -78,7 +86,7 @@ function legacy_test(context)
7886

7987
function publishTask(context)
8088
% publish HTML inline documentation strings to individual HTML files
81-
outdir = fullfile(context.Plan.RootFolder, "docs");
89+
outdir = context.Plan.RootFolder + "/docs";
8290

8391
publish_gen_index_html("stdlib", ...
8492
"A standard library of functions for Matlab.", ...
@@ -87,6 +95,68 @@ function publishTask(context)
8795
end
8896

8997

98+
function subprocess_build_c(context)
99+
100+
td = context.Plan.RootFolder + "/test";
101+
src_c = td + "/main.c";
102+
exe = td + "/printer_c.exe";
103+
104+
ccObj = mex.getCompilerConfigurations('c');
105+
106+
cc = ccObj.Details.CompilerExecutable;
107+
108+
outFlag = "-o";
109+
shell = "";
110+
shell_arg = "";
111+
msvcLike = ispc && endsWith(cc, "cl");
112+
if msvcLike
113+
shell = strtrim(ccObj.Details.CommandLineShell);
114+
shell_arg = ccObj.Details.CommandLineShellArg;
115+
outFlag = "/link /out:";
116+
end
117+
118+
cmd = join([cc, src_c, outFlag, exe]);
119+
if shell ~= ""
120+
cmd = join([shell, shell_arg, cmd]);
121+
end
122+
123+
[r, m] = system(cmd);
124+
if r ~= 0
125+
warning("failed to build TestSubprocess printer_c.exe " + m)
126+
end
127+
128+
end
129+
130+
131+
function subprocess_build_fortran(context)
132+
133+
td = context.Plan.RootFolder + "/test";
134+
src = td + "/main.f90";
135+
exe = td + "/printer_fortran.exe";
136+
137+
fcObj = mex.getCompilerConfigurations('Fortran');
138+
if isempty(fcObj)
139+
fc = getenv("FC");
140+
if isempty(fc)
141+
warning("set FC environment variable to the Fortran compiler executable, or do 'mex -setup fortran' to configure the Fortran compiler")
142+
return
143+
end
144+
else
145+
fc = fcObj.Details.CompilerExecutable;
146+
end
147+
148+
outFlag = "-o";
149+
150+
cmd = join([fc, src, outFlag, exe]);
151+
152+
[r, m] = system(cmd);
153+
if r ~= 0
154+
warning("failed to build TestSubprocess printer_fortran.exe " + m)
155+
end
156+
157+
end
158+
159+
90160
function srcs = get_mex_sources(build_all)
91161
arguments
92162
build_all (1,1) logical = false

test/TestSubprocess.m

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,32 @@ function java_required(tc)
99

1010
methods (Test)
1111

12-
function test_simple_run(tc)
12+
function test_exe_c(tc)
13+
import matlab.unittest.constraints.IsFile
14+
15+
cwd = fileparts(mfilename('fullpath'));
16+
exe = cwd + "/printer_c.exe";
17+
tc.assumeThat(exe, IsFile, exe + " not found")
18+
19+
[status, msg, err] = stdlib.subprocess_run(exe);
20+
tc.assertEqual(status, 0, err)
21+
tc.verifyEqual(msg, "stdout")
22+
tc.verifyEqual(err, "stderr")
1323

14-
if ispc
15-
c = ["cmd", "/c", "dir"];
16-
else
17-
c = 'ls';
1824
end
1925

20-
[status, msg, err] = stdlib.subprocess_run(c);
26+
27+
function test_exe_fortran(tc)
28+
import matlab.unittest.constraints.IsFile
29+
30+
cwd = fileparts(mfilename('fullpath'));
31+
exe = cwd + "/printer_fortran.exe";
32+
tc.assumeThat(exe, IsFile, exe + " not found")
33+
34+
[status, msg, err] = stdlib.subprocess_run(exe);
2135
tc.assertEqual(status, 0, err)
22-
tc.verifyGreaterThan(strlength(msg), 0)
23-
tc.verifyEqual(strlength(err), 0)
36+
tc.verifyEqual(msg, "stdout")
37+
tc.verifyEqual(err, "stderr")
2438

2539
end
2640

@@ -41,7 +55,7 @@ function test_cwd(tc)
4155

4256
td = tc.createTemporaryFolder();
4357

44-
[s, mc, e] = stdlib.subprocess_run(c, "cwd", td);
58+
[s, mc, e] = stdlib.subprocess_run(c, cwd=td);
4559
tc.assertEqual(s, 0, "status non-zero")
4660
tc.verifyNotEqual(m, mc, "expected different directory to have different contents")
4761
tc.verifyEqual(strlength(e), 0, e)

test/main.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <stdio.h>
22

33
int main(void){
4-
printf("Hello\n");
4+
printf("stdout\n");
5+
fprintf(stderr, "stderr\n");
56
return 0;
67
}

test/main.f90

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
program main
2+
3+
use, intrinsic :: iso_fortran_env
4+
5+
implicit none
6+
7+
write(output_unit, '(a)') "stdout"
8+
write(error_unit, '(a)') "stderr"
9+
10+
end program

0 commit comments

Comments
 (0)