|
72 | 72 |
|
73 | 73 | function results = run_octave_tests(test_dir) |
74 | 74 | %RUN_OCTAVE_TESTS Run function-based tests for Octave compatibility. |
75 | | - add_fastsense_private_path(); |
| 75 | +% Each test runs in a separate Octave subprocess to survive the known |
| 76 | +% Octave 8.x crash during handle-class cleanup (break_closure_cycles). |
76 | 77 | files = dir(fullfile(test_dir, 'test_*.m')); |
| 78 | + repo_root = fileparts(test_dir); |
77 | 79 |
|
78 | 80 | total = 0; |
79 | 81 | passed = 0; |
80 | 82 | failed = 0; |
81 | 83 | failures = {}; |
| 84 | + marker = '__OCTAVE_TEST_PASSED__'; |
| 85 | + newline_char = sprintf('\n'); |
| 86 | + |
| 87 | + fprintf('=== FastSense Test Suite (Octave – subprocess isolation) ===\n\n'); |
| 88 | + fprintf('Discovered %d test files.\n\n', numel(files)); |
82 | 89 |
|
83 | 90 | for i = 1:numel(files) |
84 | 91 | [~, name, ~] = fileparts(files(i).name); |
85 | 92 | fprintf('Running %s...\n', name); |
86 | | - try |
87 | | - feval(name); |
| 93 | + |
| 94 | + % Run each test in an isolated subprocess so that an Octave |
| 95 | + % crash (e.g. break_closure_cycles) cannot kill the suite. |
| 96 | + eval_str = sprintf( ... |
| 97 | + 'addpath(''%s''); install(); cd(''%s''); add_fastsense_private_path(); %s(); fprintf(''%s\\n'');', ... |
| 98 | + repo_root, test_dir, name, marker); |
| 99 | + cmd = sprintf( ... |
| 100 | + 'octave --no-gui --no-init-file --quiet --eval "%s" 2>&1', ... |
| 101 | + eval_str); |
| 102 | + [status, output] = system(cmd); |
| 103 | + |
| 104 | + % Parse output: look for success marker, strip noise |
| 105 | + lines = strsplit(output, newline_char); |
| 106 | + clean = {}; |
| 107 | + test_ok = false; |
| 108 | + for j = 1:numel(lines) |
| 109 | + ln = lines{j}; |
| 110 | + if strcmp(strtrim(ln), marker) |
| 111 | + test_ok = true; |
| 112 | + continue; |
| 113 | + end |
| 114 | + if isempty(strtrim(ln)); continue; end |
| 115 | + if strncmp(ln, 'octave:', 7); continue; end |
| 116 | + clean{end+1} = ln; |
| 117 | + end |
| 118 | + |
| 119 | + for j = 1:numel(clean) |
| 120 | + fprintf(' %s\n', clean{j}); |
| 121 | + end |
| 122 | + |
| 123 | + if test_ok |
88 | 124 | fprintf(' PASSED\n'); |
89 | 125 | passed = passed + 1; |
90 | | - catch e |
91 | | - fprintf(' FAILED: %s\n', e.message); |
| 126 | + else |
| 127 | + fprintf(' FAILED (exit code %d)\n', status); |
92 | 128 | failed = failed + 1; |
93 | | - failures{end+1} = sprintf('%s: %s', name, e.message); |
| 129 | + failures{end+1} = sprintf('%s: %s', name, ... |
| 130 | + strjoin(clean, ' | ')); |
94 | 131 | end |
95 | 132 | total = total + 1; |
96 | | - % Write results incrementally so they survive Octave crashes |
| 133 | + |
| 134 | + % Write results incrementally so they survive crashes |
97 | 135 | resultsFile = getenv('FASTSENSE_RESULTS_FILE'); |
98 | 136 | if ~isempty(resultsFile) |
99 | 137 | fid = fopen(resultsFile, 'w'); |
|
102 | 140 | end |
103 | 141 | end |
104 | 142 |
|
105 | | - fprintf('\n=== Results: %d/%d passed, %d failed ===\n', passed, total, failed); |
| 143 | + fprintf('\n=== Results: %d/%d passed, %d failed ===\n', ... |
| 144 | + passed, total, failed); |
106 | 145 | if ~isempty(failures) |
107 | 146 | fprintf('\nFailures:\n'); |
108 | 147 | for i = 1:numel(failures) |
|
0 commit comments