Skip to content

Commit 957b0d6

Browse files
committed
test:subprocess: unify tests with abstract base class
1 parent 4f3785c commit 957b0d6

10 files changed

Lines changed: 223 additions & 283 deletions

+stdlib/dotnet_api.m

Lines changed: 0 additions & 12 deletions
This file was deleted.

+stdlib/dotnet_home.m

Lines changed: 0 additions & 17 deletions
This file was deleted.

+stdlib/dotnet_version.m

Lines changed: 0 additions & 12 deletions
This file was deleted.

+stdlib/subprocess_run.m

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
%% SUBPROCESS_RUN run process
22
%
3-
% with optional cwd, env. vars, stdin, timeout
4-
%
53
% handles command lines with spaces
64
% input each segment of the command as an element in a string array
75
% this is how python subprocess.run works
86
%
97
%%% Inputs
108
% * cmd: command line. Windows paths should use filesep '\'
11-
% * opt.env: environment variable struct to set
12-
% * opt.cwd: working directory to use while running command
13-
% * opt.stdin: string to pass to subprocess stdin pipe
14-
% * opt.stdout: logical to indicate whether to use pipe for stdout
15-
% * opt.stderr: logical to indicate whether to use pipe for stderr
9+
% * env: environment variable struct to set
10+
% * cwd: working directory to use while running command
11+
% * stdin: string to pass to subprocess stdin pipe
12+
% * stdout: logical to indicate whether to use pipe for stdout
13+
% * stderr: logical to indicate whether to use pipe for stderr
1614
%%% Outputs
1715
% * status: 0 is generally success. Other codes as per the
1816
% program / command run
@@ -25,7 +23,7 @@
2523
%
2624
% NOTE: if cwd option used, any paths must be absolute, or they are relative to pwd.
2725

28-
function [status, msg] = subprocess_run(cmd, opt)
26+
function [status, msg, err] = subprocess_run(cmd, opt)
2927
arguments
3028
cmd (1,:) string
3129
opt.env (1,1) struct = struct()
@@ -36,9 +34,13 @@
3634
opt.echo (1,1) logical = false
3735
end
3836

37+
err = string.empty;
38+
% for compatibility with implementations that return stderr separately.
39+
% Matlab system() returns combined stdout and stderr in msg, and leaves err empty.
40+
3941

4042
if ~stdlib.strempty(opt.cwd)
41-
assert(isfolder(opt.cwd), opt.cwd + " is not a folder")
43+
mustBeFolder(opt.cwd)
4244

4345
cmd = join(["cd", opt.cwd, stdlib.cmdsep(), cmd]);
4446
end
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
%% JAVA_RUN run process for Matlab only
1+
%% SUBPROCESS_RUN_JAVA run process using Java ProcessBuilder interface
22
%
33
% with optional cwd, env. vars, stdin, timeout
44
%
@@ -8,30 +8,30 @@
88
%
99
%%% Inputs
1010
% * cmd_array: vector of string to compose a command line
11-
% * opt.env: environment variable struct to set
12-
% * opt.cwd: working directory to use while running command
13-
% * opt.stdin: string to pass to subprocess stdin pipe - OK to use with timeout
14-
% * opt.timeout: time to wait for process to complete before erroring (seconds)
15-
% * opt.stdout: logical to indicate whether to use pipe for stdout
16-
% * opt.stderr: logical to indicate whether to use pipe for stderr
11+
% * env: environment variable struct to set
12+
% * cwd: working directory to use while running command
13+
% * stdin: string to pass to subprocess stdin pipe - OK to use with timeout
14+
% * timeout: time to wait for process to complete before erroring (seconds)
15+
% * stdout: logical to indicate whether to use pipe for stdout
16+
% * stderr: logical to indicate whether to use pipe for stderr
1717
%%% Outputs
1818
% * status: 0 is generally success. -1 if timeout. Other codes as per the
1919
% program / command run
2020
% * stdout: stdout from process
2121
% * stderr: stderr from process
2222
%
2323
%% Example
24-
% subprocess_run(["mpiexec", "-help2"])
25-
% subprocess_run(["sh", "-c", "ls", "-l"])
26-
% subprocess_run(["cmd", "/c", "dir", "/Q", "/L"])
24+
% subprocess_run_java(["mpiexec", "-help2"])
25+
% subprocess_run_java(["sh", "-c", "ls", "-l"])
26+
% subprocess_run_java(["cmd", "/c", "dir", "/Q", "/L"])
2727
%
2828
% NOTE: if cwd option used, any paths must be absolute or relative to cwd.
2929
% otherwise, they are relative to pwd.
3030
%
3131
% uses Matlab Java ProcessBuilder interface to run subprocess and use stdin/stdout pipes
3232
% https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ProcessBuilder.html
3333

34-
function [status, stdout, stderr] = java_run(cmd, opt)
34+
function [status, stdout, stderr] = subprocess_run_java(cmd, opt)
3535
arguments
3636
cmd (1,:) string
3737
opt.env (1,1) struct = struct()
@@ -40,8 +40,11 @@
4040
opt.timeout (1,1) int64 = 0
4141
opt.stdout (1,1) logical = true
4242
opt.stderr (1,1) logical = true
43+
opt.echo (1,1) logical = false
4344
end
4445

46+
% opt.echo is unused, but is a real option of subprocess_run()
47+
4548
if (opt.stdout || opt.stderr) && opt.timeout > 0
4649
error("stderr or stdout and timeout options are mutually exclusive")
4750
end
@@ -152,7 +155,7 @@
152155

153156
line = reader.readLine();
154157
while ~isempty(line)
155-
msg = stdlib.append(msg, string(line), newline);
158+
msg = join([msg, string(line)], newline);
156159
line = reader.readLine();
157160
end
158161
msg = strip(msg);

Readme_java.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ to a compatible Java library by using the
5959
[jenv](https://www.mathworks.com/help/matlab/ref/jenv.html)
6060
Matlab function.
6161

62+
### macOS JVM configuration
63+
6264
For example, to use the
6365
[JDK 17 on macOS](https://www.oracle.com/java/technologies/downloads/#jdk17-mac)
6466
download and extract the ARM64 Compressed Archive.
@@ -74,12 +76,30 @@ Or for the Amazon Corretto JDK 11 on macOS:
7476
jenv("/Library/Java/JavaVirtualMachines/amazon-corretto-11.jdk/Contents/Home/")
7577
```
7678

79+
### Windows JVM configuration
80+
7781
On Windows, obtain OpenJDK with WinGet, which installs under "$Env:ProgramFiles/Microsoft/jdk-*":
7882

7983
```sh
80-
winget install Microsoft.OpenJDK.21
84+
winget search Microsoft.OpenJDK
85+
```
86+
87+
Suppose OpenJDK 25 is desired and available like:
88+
89+
```sh
90+
winget install Microsoft.OpenJDK.25
8191
```
8292

93+
Then tell Matlab to use this JDK from the Matlab console by:
94+
95+
```matlab
96+
jp = ls(fullfile(getenv('ProgramFiles'), 'Microsoft/jdk-25*'));
97+
mustBeTextScalar(jp)
98+
jp = fullfile(getenv('ProgramFiles'), 'Microsoft', jp);
99+
jenv(jp)
100+
101+
## Factory Reset JRE
102+
83103
To
84104
[revert back to the default JRE](https://www.mathworks.com/help/matlab/ref/matlab_jenv.html)
85105
if Matlab can't start or has problems, from system Terminal (not within Matlab):

buildfile.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
exes = fullfile(test_root, exes);
102102

103103
plan("exe") = matlab.buildtool.Task(Inputs=srcs, Outputs=exes, Actions=@build_exe, ...
104-
Description="build demo executables for testing java_run");
104+
Description="build demo executables for testing subprocess_run");
105105

106106
end
107107

test/SubprocessRunAbstract.m

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
classdef (Abstract) SubprocessRunAbstract < matlab.unittest.TestCase
2+
% Abstract base class for testing subprocess_run* functions
3+
4+
properties (Abstract, Constant)
5+
% Concrete subclasses must define this
6+
runFcn % Function handle
7+
end
8+
9+
properties
10+
CI = is_ci()
11+
end
12+
13+
properties (TestParameter)
14+
lang_out = {"c", "fortran"}
15+
lang_in = {"cpp", "fortran"}
16+
end
17+
18+
methods(TestClassSetup)
19+
function test_dirs(tc)
20+
tc.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture())
21+
end
22+
end
23+
24+
25+
methods (Test)
26+
% Common tests that apply to all implementations
27+
28+
function test_subprocess_cwd(tc)
29+
30+
if ispc
31+
if isequal(tc.runFcn, @stdlib.subprocess_run_java)
32+
c = ["cmd", "/c", "cd"];
33+
else
34+
c = "cd";
35+
end
36+
else
37+
c = "pwd";
38+
end
39+
40+
% 'echo' true is optional, just for debugging command.
41+
debug = true;
42+
43+
[s, m, e] = tc.runFcn(c, 'echo', debug);
44+
tc.assertEqual(s, 0, "status non-zero")
45+
tc.verifyGreaterThan(strlength(m), 0, "empty directory not expected")
46+
47+
[s, mc, ec] = tc.runFcn(c, 'cwd', matlabroot, 'echo', debug);
48+
tc.assertEqual(s, 0, "status non-zero")
49+
50+
if isequal(tc.runFcn, @stdlib.subprocess_run_java)
51+
tc.verifyEqual(strlength(e), 0, e)
52+
tc.verifyEqual(strlength(e), 0, ec)
53+
end
54+
55+
tc.assumeFalse(strcmp(m, mc) && tc.CI, "Some CI block cwd changes")
56+
tc.verifyNotEqual(m, mc, "expected different directory to have different contents")
57+
end
58+
59+
60+
function test_subprocess_env_run(tc)
61+
62+
cwd = fileparts(mfilename('fullpath'));
63+
exe = cwd + "/printenv.exe";
64+
tc.assertThat(exe, matlab.unittest.constraints.IsFile)
65+
66+
names = ["TEST1", "TEST2"];
67+
vals = ["test123", "test321"];
68+
69+
env = struct(names(1), vals(1), names(2), vals(2));
70+
71+
for i = 1:length(names)
72+
[ret, out] = tc.runFcn([exe, names(i)], 'env', env);
73+
tc.verifyEqual(ret, 0)
74+
tc.verifySubstring(out, vals(i))
75+
end
76+
end
77+
78+
79+
function test_subprocess_stdout_stderr(tc, lang_out)
80+
81+
cwd = fileparts(mfilename('fullpath'));
82+
exe = fullfile(cwd, "stdout_stderr_" + lang_out + ".exe");
83+
84+
switch lang_out
85+
case "fortran", tc.assumeThat(exe, matlab.unittest.constraints.IsFile)
86+
case {"c", "cpp"}, tc.assertThat(exe, matlab.unittest.constraints.IsFile)
87+
otherwise, tc.assertTrue(false, "Unknown language: " + lang_out)
88+
end
89+
90+
[status, msg, err] = tc.runFcn(exe);
91+
92+
if ispc()
93+
tc.assumeNotEqual(status, -1073741515, "GCC DLLs probably not on PATH")
94+
end
95+
96+
tc.assertEqual(status, 0, err)
97+
98+
tc.verifySubstring(msg, 'stdout')
99+
if isequal(tc.runFcn, @stdlib.subprocess_run_java)
100+
tc.verifyEqual(err, "stderr")
101+
else
102+
tc.verifySubstring(msg, 'stderr')
103+
end
104+
105+
[status, msg] = tc.runFcn(exe, 'stdout', false);
106+
tc.assertEqual(status, 0)
107+
if isequal(tc.runFcn, @stdlib.subprocess_run_java)
108+
tc.verifyTrue(stdlib.strempty(msg))
109+
elseif isequal(tc.runFcn, @stdlib.subprocess_run)
110+
tc.verifyEqual(msg, 'stderr')
111+
end
112+
113+
[status, msg] = tc.runFcn(exe, 'stderr', false);
114+
tc.assertEqual(status, 0)
115+
tc.verifySubstring(msg, 'stdout')
116+
end
117+
118+
119+
function test_subprocess_stdin(tc, lang_in)
120+
121+
cwd = fileparts(mfilename('fullpath'));
122+
exe = fullfile(cwd, "stdin_" + lang_in + ".exe");
123+
124+
switch lang_in
125+
case "fortran", tc.assumeThat(exe, matlab.unittest.constraints.IsFile)
126+
case {"c", "cpp"}, tc.assertThat(exe, matlab.unittest.constraints.IsFile)
127+
otherwise, tc.assertTrue(false, "Unknown language: " + lang_in)
128+
end
129+
130+
[status, msg, err] = tc.runFcn(exe, 'stdin', "1 2");
131+
132+
if ispc()
133+
tc.assumeNotEqual(status, -1073741515, "GCC DLLs probably not on PATH")
134+
end
135+
136+
tc.assertEqual(status, 0, err)
137+
tc.verifyMatches(msg, "^3$")
138+
tc.verifyTrue(stdlib.strempty(err))
139+
end
140+
141+
end
142+
end

0 commit comments

Comments
 (0)