function app = RUN_TESTS(options) %TEST_DASHBOARD Minimal GUI for test discovery and execution. % test_dashboard() opens a small dashboard that: % - discovers implemented, placeholder, and missing class-backed tests % - lists standalone tests such as integration or helper tests % - runs the selected test file % - runs the existing fast/full test profiles % % app = test_dashboard("Visible", false) builds the dashboard without % showing it. This is useful for non-interactive validation. arguments options.Visible (1, 1) logical = true end app = struct(); app.Inventory = table(); app.SelectedRow = []; app.ActiveProfileRowIndices = []; app.LastRunStatusMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); app.LastRunSummaryMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); app.LastRunDateMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); app.LastRunCommitMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); testsRoot = fileparts(mfilename("fullpath")); repoRoot = fileparts(testsRoot); reportsRoot = fullfile(testsRoot, 'reports'); latestRunFile = fullfile(testsRoot, '.last_test_run.json'); createUi(); refreshInventory(); if options.Visible app.Figure.Visible = "on"; end function createUi() app.Figure = uifigure( ... "Name", "IMDD Test Dashboard", ... "Position", [80, 80, 1360, 820], ... "Color", [0.965, 0.973, 0.988], ... "Visible", "off"); app.Layout = uigridlayout(app.Figure, [3, 1]); app.Layout.RowHeight = {92, "1x", 150}; app.Layout.Padding = [14, 14, 14, 14]; app.Layout.RowSpacing = 12; app.HeaderPanel = uipanel(app.Layout, ... "Title", "Suite Overview", ... "BackgroundColor", [0.985, 0.989, 0.997]); app.HeaderPanel.Layout.Row = 1; headerGrid = uigridlayout(app.HeaderPanel, [2, 10]); headerGrid.RowHeight = {28, 32}; headerGrid.ColumnWidth = {110, 110, 110, 90, 90, 90, "1x", 120, 120, 120}; headerGrid.Padding = [10, 8, 10, 8]; app.ImplementedLabel = makeMetricLabel(headerGrid, "Implemented", 1); app.PlaceholderLabel = makeMetricLabel(headerGrid, "Placeholder", 2); app.MissingLabel = makeMetricLabel(headerGrid, "Missing", 3); app.PassedLabel = makeMetricLabel(headerGrid, "Passed", 4); app.FailedLabel = makeMetricLabel(headerGrid, "Failed", 5); app.IncompleteLabel = makeMetricLabel(headerGrid, "Incomplete", 6); app.TotalLabel = makeMetricLabel(headerGrid, "Total", 7); app.FilterDropDown = uidropdown(headerGrid, ... "Items", {'All', 'Implemented', 'Missing', 'Standalone'}, ... "Value", 'All', ... "ValueChangedFcn", @filterChanged); app.FilterDropDown.Layout.Row = 2; app.FilterDropDown.Layout.Column = 8; app.RefreshButton = uibutton(headerGrid, ... "Text", "Refresh", ... "ButtonPushedFcn", @refreshInventoryButtonPushed); app.RefreshButton.Layout.Row = 2; app.RefreshButton.Layout.Column = 9; app.OpenButton = uibutton(headerGrid, ... "Text", "Open Selected", ... "ButtonPushedFcn", @openSelectedTest); app.OpenButton.Layout.Row = 2; app.OpenButton.Layout.Column = 10; app.TablePanel = uipanel(app.Layout, ... "Title", "Discovered Tests", ... "BackgroundColor", [0.985, 0.989, 0.997]); app.TablePanel.Layout.Row = 2; tableGrid = uigridlayout(app.TablePanel, [2, 1]); tableGrid.RowHeight = {36, "1x"}; tableGrid.Padding = [10, 8, 10, 10]; buttonGrid = uigridlayout(tableGrid, [1, 6]); buttonGrid.ColumnWidth = {150, 120, 120, 140, 170, "1x"}; buttonGrid.Padding = [0, 0, 0, 0]; buttonGrid.ColumnSpacing = 10; app.RunSelectedButton = uibutton(buttonGrid, ... "Text", "Run Selected", ... "ButtonPushedFcn", @runSelectedTest); app.RunFastButton = uibutton(buttonGrid, ... "Text", "Run Fast", ... "ButtonPushedFcn", @(~, ~) runProfile("fast")); app.RunFullButton = uibutton(buttonGrid, ... "Text", "Run Full", ... "ButtonPushedFcn", @(~, ~) runProfile("full")); app.RunIntegrationButton = uibutton(buttonGrid, ... "Text", "Run Integration", ... "ButtonPushedFcn", @(~, ~) runProfile("integration")); app.GenerateButton = uibutton(buttonGrid, ... "Text", "Generate Missing Tests", ... "ButtonPushedFcn", @generateMissingTests); app.TableHintLabel = uilabel(buttonGrid, ... "Text", "Select a row to run or open the corresponding test file.", ... "HorizontalAlignment", "right", ... "FontColor", [0.28, 0.32, 0.38]); app.TableHintLabel.Layout.Column = 6; app.Table = uitable(tableGrid, ... "ColumnName", {"Status", "Last Run", "Result", "Date", "Commit", "Scope", "Target", "Test File", "Tags"}, ... "ColumnEditable", false(1, 9), ... "ColumnSortable", true(1, 9), ... "ColumnWidth", {95, 95, 90, 150, 90, 100, 240, 300, "auto"}, ... "SelectionType", "row", ... "Multiselect", "off", ... "CellSelectionCallback", @tableSelectionChanged, ... "FontName", "Consolas"); app.Table.Layout.Row = 2; app.LogPanel = uipanel(app.Layout, ... "Title", "Run Log", ... "BackgroundColor", [0.985, 0.989, 0.997]); app.LogPanel.Layout.Row = 3; logGrid = uigridlayout(app.LogPanel, [1, 1]); logGrid.Padding = [10, 8, 10, 10]; app.LogArea = uitextarea(logGrid, ... "Editable", "off", ... "FontName", "Consolas", ... "Value", {'IMDD Test Dashboard ready.'}); end function label = makeMetricLabel(parent, titleText, columnIdx) label = uilabel(parent, ... "Text", sprintf("%s: --", titleText), ... "FontWeight", "bold", ... "FontSize", 13, ... "FontColor", [0.12, 0.18, 0.26]); label.Layout.Row = 1; label.Layout.Column = columnIdx; end function refreshInventoryButtonPushed(~, ~) refreshInventory(); end function refreshInventory() app.Inventory = buildInventory(repoRoot, testsRoot); app.SelectedRow = []; loadPersistedRunState(); updateSummary(app.Inventory); applyFilter(); appendLog(sprintf("[%s] Refreshed test inventory.", stamp())); end function filterChanged(~, ~) applyFilter(); end function applyFilter() filtered = app.Inventory; switch string(app.FilterDropDown.Value) case "Implemented" filtered = filtered(filtered.Status == "Implemented", :); case "Missing" filtered = filtered(filtered.Status == "Missing", :); case "Standalone" filtered = filtered(filtered.Scope == "Standalone", :); otherwise % keep all rows end app.Table.Data = filtered(:, ["Status", "LastRun", "ResultSummary", "LastRunDate", "LastRunCommit", "Scope", "Target", "TestFile", "Tags"]); applyRowStyles(filtered); end function updateSummary(inventory) implementedCount = nnz(inventory.Status == "Implemented"); missingCount = nnz(inventory.Status == "Missing"); passedCount = nnz(inventory.LastRun == "Passed"); failedCount = nnz(inventory.LastRun == "Failed"); incompleteCount = nnz(inventory.LastRun == "Incomplete"); totalCount = height(inventory); app.ImplementedLabel.Text = sprintf("Implemented: %d", implementedCount); app.PlaceholderLabel.Text = "Placeholder: hidden"; app.MissingLabel.Text = sprintf("Missing: %d", missingCount); app.PassedLabel.Text = sprintf("Passed: %d", passedCount); app.FailedLabel.Text = sprintf("Failed: %d", failedCount); app.IncompleteLabel.Text = sprintf("Incomplete: %d", incompleteCount); app.TotalLabel.Text = sprintf("Total: %d", totalCount); app.PassedLabel.FontColor = metricColor(passedCount > 0, [0.18, 0.56, 0.24]); app.FailedLabel.FontColor = metricColor(failedCount > 0, [0.78, 0.22, 0.22]); app.IncompleteLabel.FontColor = metricColor(incompleteCount > 0, [0.72, 0.48, 0.08]); end function applyRowStyles(filtered) removeStyle(app.Table); greenStyle = uistyle("BackgroundColor", [0.90, 0.97, 0.92]); amberStyle = uistyle("BackgroundColor", [0.995, 0.95, 0.85]); redStyle = uistyle("BackgroundColor", [0.985, 0.90, 0.90]); blueStyle = uistyle("BackgroundColor", [0.90, 0.94, 0.995]); runningStyle = uistyle("BackgroundColor", [0.89, 0.95, 1.00]); for rowIdx = 1:height(filtered) if filtered.LastRun(rowIdx) == "Passed" addStyle(app.Table, greenStyle, "row", rowIdx); elseif filtered.LastRun(rowIdx) == "Failed" addStyle(app.Table, redStyle, "row", rowIdx); elseif filtered.LastRun(rowIdx) == "Incomplete" addStyle(app.Table, amberStyle, "row", rowIdx); elseif filtered.LastRun(rowIdx) == "Running" addStyle(app.Table, runningStyle, "row", rowIdx); elseif filtered.Scope(rowIdx) == "Standalone" addStyle(app.Table, blueStyle, "row", rowIdx); elseif filtered.Status(rowIdx) == "Implemented" addStyle(app.Table, greenStyle, "row", rowIdx); elseif filtered.Status(rowIdx) == "Placeholder" addStyle(app.Table, amberStyle, "row", rowIdx); else addStyle(app.Table, redStyle, "row", rowIdx); end end end function tableSelectionChanged(src, event) if isempty(event.Indices) app.SelectedRow = []; return end selectedRow = event.Indices(1); visibleRows = src.Data; selectedTestFile = string(visibleRows.TestFile(selectedRow)); matchIdx = find(app.Inventory.TestFile == selectedTestFile, 1); if isempty(matchIdx) selectedTarget = string(visibleRows.Target(selectedRow)); matchIdx = find(app.Inventory.Target == selectedTarget, 1); end app.SelectedRow = matchIdx; end function openSelectedTest(~, ~) record = selectedRecord(); if isempty(record) uialert(app.Figure, "Select a test row first.", "No Selection"); return end if strlength(record.TestFile) == 0 || ~isfile(record.TestFile) uialert(app.Figure, "The selected class does not have a test file yet.", "No Test File"); return end edit(record.TestFile); appendLog(sprintf("[%s] Opened %s", stamp(), record.TestFile)); end function runSelectedTest(~, ~) record = selectedRecord(); if isempty(record) uialert(app.Figure, "Select a test row first.", "No Selection"); return end if strlength(record.TestFile) == 0 || ~isfile(record.TestFile) uialert(app.Figure, "The selected class does not have a runnable test file yet.", "Missing Test"); return end titleText = sprintf("Running %s", record.TestFileName); markRowAsRunning(record); runWithProgress(titleText, @() runSingleTestFile(record.TestFile), record); end function runProfile(profile) titleText = sprintf("Running profile '%s'", profile); rowIndices = profileRowIndices(profile); app.ActiveProfileRowIndices = rowIndices; markRowsAsRunning(rowIndices); runWithProgress(titleText, @() runProfileInternal(profile, rowIndices), table()); app.ActiveProfileRowIndices = []; end function generateMissingTests(~, ~) setupRepositoryPath(); try summary = generateTests("verbose", false); refreshInventory(); appendLog(sprintf("[%s] Generated missing tests: %d new, %d existing skipped, %d classes tracked.", ... stamp(), summary.generated, summary.skippedExisting, summary.classCount)); catch err appendLog(sprintf("[%s] generateTests failed.", stamp())); appendLog(getReport(err, "extended", "hyperlinks", "off")); uialert(app.Figure, err.message, "Generate Tests Failed"); end end function runWithProgress(titleText, runner, record) try [output, results] = runner(); appendLog(sprintf("[%s] %s", stamp(), titleText)); appendLog(output); updateRunStateFromResults(results, record); writeRunArtifacts(titleText, results, record); catch err appendLog(sprintf("[%s] %s failed.", stamp(), titleText)); appendLog(getReport(err, "extended", "hyperlinks", "off")); updateFailureState(record); uialert(app.Figure, err.message, "Run Failed"); end end function [output, results] = runProfileInternal(profile, rowIndices) setupRepositoryPath(); profile = string(profile); results = matlab.unittest.TestResult.empty(0, 1); logLines = strings(0, 1); logLines(end + 1, 1) = sprintf("Running profile '%s' with %d test files.", profile, numel(rowIndices)); for idx = 1:numel(rowIndices) record = app.Inventory(rowIndices(idx), :); if strlength(record.TestFile) == 0 || ~isfile(record.TestFile) continue end logLines(end + 1, 1) = sprintf("Running %s", record.TestFileName); drawnow; currentResults = runtests(char(record.TestFile)); currentResults = currentResults(:); if isempty(results) results = currentResults; else results = [results; currentResults]; %#ok end updateRunStateFromResults(currentResults, record); [statusText, summaryText] = summarizeResults(currentResults); logLines(end + 1, 1) = sprintf("Finished %s: %s (%s)", ... record.TestFileName, statusText, summaryText); drawnow; end logLines(end + 1, 1) = sprintf("Passed: %d | Failed: %d | Incomplete: %d", ... nnz([results.Passed]), nnz([results.Failed]), nnz([results.Incomplete])); output = strjoin(logLines, newline); end function [output, results] = runSingleTestFile(testFile) setupRepositoryPath(); results = runtests(char(testFile)); output = evalc('disp(results);'); end function record = selectedRecord() record = table(); if isempty(app.SelectedRow) return end if app.SelectedRow < 1 || app.SelectedRow > height(app.Inventory) return end record = app.Inventory(app.SelectedRow, :); end function setupRepositoryPath() addpath(repoRoot); addpath(testsRoot); folders = ["Classes", "Functions", "Datatypes", "Libs"]; for folder = folders fullFolder = fullfile(repoRoot, folder); if isfolder(fullFolder) addpath(genpath(fullFolder)); end end end function appendLog(textIn) newLines = string(splitlines(string(textIn))); current = string(app.LogArea.Value); current = current(current ~= ""); newLines = newLines(newLines ~= ""); combined = [current; newLines]; if isempty(combined) combined = " "; end app.LogArea.Value = cellstr(combined); drawnow; end function markRowAsRunning(record) if isempty(record) || strlength(record.TestFile) == 0 return end updateMappedState(char(record.TestFile), 'Running', '...'); applyStoredRunState(); pause(0.05); end function markRowsAsRunning(rowIndices) for idx = 1:numel(rowIndices) key = char(app.Inventory.TestFile(rowIndices(idx))); if ~isempty(key) updateMappedState(key, 'Running', '...'); end end applyStoredRunState(); pause(0.05); end function updateRunStateFromResults(results, record) if ~isempty(record) [statusText, summaryText] = summarizeResults(results); updateMappedState(char(record.TestFile), statusText, summaryText); else resultNames = string({results.Name})'; uniqueNames = unique(resultNames); for idx = 1:numel(uniqueNames) currentResults = results(resultNames == uniqueNames(idx)); [statusText, summaryText] = summarizeResults(currentResults); targetRows = find(app.Inventory.TestFileName == uniqueNames(idx)); for rowIdx = targetRows' updateMappedState(char(app.Inventory.TestFile(rowIdx)), statusText, summaryText); end end end applyStoredRunState(); end function updateFailureState(record) if ~isempty(record) && strlength(record.TestFile) > 0 updateMappedState(char(record.TestFile), 'Failed', 'error'); applyStoredRunState(); end end function updateMappedState(key, statusText, summaryText) if isempty(key) return end app.LastRunStatusMap(key) = char(statusText); app.LastRunSummaryMap(key) = char(summaryText); end function applyStoredRunState() for idx = 1:height(app.Inventory) key = char(app.Inventory.TestFile(idx)); if isempty(key) continue end if isKey(app.LastRunStatusMap, key) app.Inventory.LastRun(idx) = string(app.LastRunStatusMap(key)); app.Inventory.ResultSummary(idx) = string(app.LastRunSummaryMap(key)); if isKey(app.LastRunDateMap, key) app.Inventory.LastRunDate(idx) = string(app.LastRunDateMap(key)); end if isKey(app.LastRunCommitMap, key) app.Inventory.LastRunCommit(idx) = string(app.LastRunCommitMap(key)); end end end updateSummary(app.Inventory); applyFilter(); end function loadPersistedRunState() app.LastRunStatusMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); app.LastRunSummaryMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); app.LastRunDateMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); app.LastRunCommitMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); if ~isfile(latestRunFile) return end try payload = jsondecode(fileread(latestRunFile)); fallbackDate = stringOrEmpty(payload, 'timestamp'); fallbackCommit = ""; if isfield(payload, 'git') fallbackCommit = stringOrEmpty(payload.git, 'short_commit'); end if isfield(payload, 'row_states') rowStates = payload.row_states; for idx = 1:numel(rowStates) key = stringOrEmpty(rowStates(idx), 'test_file'); if strlength(key) == 0 continue end app.LastRunStatusMap(char(key)) = char(stringOrEmpty(rowStates(idx), 'last_run')); app.LastRunSummaryMap(char(key)) = char(stringOrEmpty(rowStates(idx), 'result_summary')); rowDate = stringOrEmpty(rowStates(idx), 'last_run_date'); rowCommit = stringOrEmpty(rowStates(idx), 'last_run_commit'); if strlength(rowDate) == 0 rowDate = fallbackDate; end if strlength(rowCommit) == 0 rowCommit = fallbackCommit; end app.LastRunDateMap(char(key)) = char(rowDate); app.LastRunCommitMap(char(key)) = char(rowCommit); end end applyStoredRunState(); catch % Ignore malformed local cache files and rebuild from scratch. end end function writeRunArtifacts(runLabel, results, record) if ~isfolder(reportsRoot) mkdir(reportsRoot); end gitInfo = collectGitMetadata(repoRoot); runTimestamp = char(datetime("now", "TimeZone", "local", "Format", "yyyy-MM-dd'T'HH:mm:ssXXX")); updateMetadataMaps(results, record, runTimestamp, gitInfo.short_commit); report = struct(); report.timestamp = runTimestamp; rowStates = inventoryRowStates(app.Inventory); report.run_label = char(runLabel); report.repo_root = repoRoot; report.git = gitInfo; report.totals = struct( ... 'passed', nnz([results.Passed]), ... 'failed', nnz([results.Failed]), ... 'incomplete', nnz([results.Incomplete])); report.method_results = resultEntries(results); report.row_states = rowStates; jsonText = jsonencode(report, PrettyPrint=true); writeTextFile(latestRunFile, jsonText); reportFile = fullfile(reportsRoot, sprintf('%s_%s.json', ... datestr(now, 'yyyy-mm-dd_HHMMSS'), sanitizeFileLabel(runLabel))); writeTextFile(reportFile, jsonText); appendLog(sprintf("[%s] Saved run report: %s", stamp(), reportFile)); end function updateMetadataMaps(results, record, runDate, shortCommit) if ~isempty(record) && strlength(record.TestFile) > 0 key = char(record.TestFile); app.LastRunDateMap(key) = char(string(runDate)); app.LastRunCommitMap(key) = char(string(shortCommit)); applyStoredRunState(); return end if ~isempty(app.ActiveProfileRowIndices) for rowIdx = app.ActiveProfileRowIndices(:)' key = char(app.Inventory.TestFile(rowIdx)); if isempty(key) continue end app.LastRunDateMap(key) = char(string(runDate)); app.LastRunCommitMap(key) = char(string(shortCommit)); end end applyStoredRunState(); end function rowIndices = profileRowIndices(profile) rowIndices = []; normalizedTags = "," + erase(app.Inventory.Tags, " ") + ","; switch string(profile) case "fast" rowIndices = find(app.Inventory.Status ~= "Missing" & ... contains(normalizedTags, ",fast,") & ... ~contains(normalizedTags, ",placeholder,") & ... ~contains(normalizedTags, ",hardware,") & ... ~contains(normalizedTags, ",slow,")); case "full" rowIndices = find(app.Inventory.Status ~= "Missing" & ... ~contains(normalizedTags, ",placeholder,") & ... ~contains(normalizedTags, ",hardware,")); case "integration" rowIndices = find(app.Inventory.Status ~= "Missing" & ... contains(normalizedTags, ",integration,") & ... ~contains(normalizedTags, ",placeholder,") & ... ~contains(normalizedTags, ",hardware,")); case "placeholder" rowIndices = find(contains(normalizedTags, ",placeholder,")); end end function textOut = stamp() textOut = char(datetime("now", "Format", "yyyy-MM-dd HH:mm:ss")); end end function color = metricColor(hasRuns, activeColor) if hasRuns color = activeColor; else color = [0.48, 0.52, 0.58]; end end function inventory = buildInventory(repoRoot, testsRoot) repoRoot = char(string(repoRoot)); testsRoot = char(string(testsRoot)); classesRoot = fullfile(repoRoot, 'Classes'); classFiles = collectMFiles(classesRoot); records = struct( ... "Status", {}, ... "LastRun", {}, ... "ResultSummary", {}, ... "LastRunDate", {}, ... "LastRunCommit", {}, ... "Scope", {}, ... "Target", {}, ... "TargetFile", {}, ... "TestFile", {}, ... "TestFileName", {}, ... "Tags", {}); matchedTestFiles = strings(0, 1); for idx = 1:numel(classFiles) classFile = fullfile(classFiles(idx).folder, classFiles(idx).name); if ~isClassFile(classFile) continue end relativePath = strrep(classFile, [classesRoot, filesep], ""); [relativeDir, className] = fileparts(relativePath); relativeDirText = char(join(string(relativeDir), "")); classNameText = char(string(className)); testFile = findMatchingTestFile(testsRoot, relativeDirText, classNameText); if strlength(testFile) == 0 status = "Missing"; tags = ""; testFileName = ""; else source = fileread(testFile); if contains(source, "assumeFail(") continue else matchedTestFiles(end + 1, 1) = testFile; %#ok status = "Implemented"; tags = strjoin(extractTestTags(source), ", "); [~, testFileName] = fileparts(testFile); end end targetLabel = fullfile('Classes', relativeDirText, [classNameText, '.m']); targetLabel = strrep(targetLabel, "\", "/"); records(end + 1) = struct( ... %#ok "Status", status, ... "LastRun", "", ... "ResultSummary", "", ... "LastRunDate", "", ... "LastRunCommit", "", ... "Scope", "Class", ... "Target", string(targetLabel), ... "TargetFile", string(classFile), ... "TestFile", string(testFile), ... "TestFileName", string(testFileName), ... "Tags", string(tags)); end allTestFiles = dir(fullfile(testsRoot, "**", "*_test.m")); for idx = 1:numel(allTestFiles) testFile = fullfile(allTestFiles(idx).folder, allTestFiles(idx).name); if any(strcmpi(testFile, matchedTestFiles)) continue end source = fileread(testFile); if contains(source, "assumeFail(") continue else status = "Implemented"; end [~, testFileName] = fileparts(testFile); relativeTest = strrep(testFile, [testsRoot, filesep], ""); relativeTest = strrep(relativeTest, "\", "/"); tags = strjoin(extractTestTags(source), ", "); records(end + 1) = struct( ... %#ok "Status", status, ... "LastRun", "", ... "ResultSummary", "", ... "LastRunDate", "", ... "LastRunCommit", "", ... "Scope", "Standalone", ... "Target", string(relativeTest), ... "TargetFile", "", ... "TestFile", string(testFile), ... "TestFileName", string(testFileName), ... "Tags", string(tags)); end inventory = struct2table(records); if isempty(inventory) inventory = table(strings(0, 1), strings(0, 1), strings(0, 1), ... strings(0, 1), strings(0, 1), strings(0, 1), strings(0, 1), ... strings(0, 1), strings(0, 1), strings(0, 1), strings(0, 1), ... "VariableNames", ["Status", "LastRun", "ResultSummary", "LastRunDate", ... "LastRunCommit", "Scope", "Target", "TargetFile", "TestFile", ... "TestFileName", "Tags"]); return end inventory = inventory(inventory.Status ~= "Placeholder", :); statusOrder = containers.Map( ... {'Implemented', 'Missing', 'Placeholder'}, ... {1, 2, 3}); scopeOrder = containers.Map({'Class', 'Standalone'}, {1, 2}); statusRank = zeros(height(inventory), 1); scopeRank = zeros(height(inventory), 1); for idx = 1:height(inventory) statusRank(idx) = statusOrder(char(inventory.Status(idx))); scopeRank(idx) = scopeOrder(char(inventory.Scope(idx))); end inventory.statusRank = statusRank; inventory.scopeRank = scopeRank; inventory = sortrows(inventory, {'scopeRank', 'statusRank', 'Target'}); inventory = removevars(inventory, {'statusRank', 'scopeRank'}); end function testFile = findMatchingTestFile(testsRoot, relativeDir, className) testsRoot = char(string(testsRoot)); relativeDir = char(join(string(relativeDir), "")); className = char(string(className)); testDir = fullfile(testsRoot, relativeDir); testFile = ""; if ~isfolder(testDir) return end exactFile = fullfile(testDir, [className, '_test.m']); if isfile(exactFile) testFile = string(exactFile); return end candidates = dir(fullfile(testDir, '*_test.m')); if isempty(candidates) return end candidateNames = string({candidates.name}); suffix = ['_', className, '_test.m']; matchMask = endsWith(candidateNames, suffix, "IgnoreCase", true); if any(matchMask) firstMatch = candidates(find(matchMask, 1)); testFile = string(fullfile(firstMatch.folder, firstMatch.name)); end end function tags = extractTestTags(source) tokens = regexp(source, "TestTags\s*=\s*\{([^}]*)\}", "tokens", "once"); if isempty(tokens) tags = strings(0, 1); return end tags = regexp(tokens{1}, "'([^']*)'", "tokens"); tags = string(cellfun(@(entry) entry{1}, tags, "UniformOutput", false)); end function tf = isClassFile(filePath) source = fileread(filePath); tf = contains(source, "classdef"); end function files = collectMFiles(rootFolder) pathString = genpath(rootFolder); folders = regexp(pathString, pathsep, "split"); folders = folders(~cellfun("isempty", folders)); files = dir(fullfile(rootFolder, "*.m")); files = files([]); %#ok files = dir(fullfile(rootFolder, "*.m")); files = files([]); for idx = 1:numel(folders) currentFiles = dir(fullfile(folders{idx}, "*.m")); if isempty(currentFiles) continue end currentFiles = currentFiles(~[currentFiles.isdir]); files = [files; currentFiles]; %#ok end end function [statusText, summaryText] = summarizeResults(results) passedCount = nnz([results.Passed]); failedCount = nnz([results.Failed]); incompleteCount = nnz([results.Incomplete]); if failedCount > 0 statusText = "Failed"; elseif incompleteCount > 0 statusText = "Incomplete"; else statusText = "Passed"; end summaryText = sprintf('%d/%d/%d', passedCount, failedCount, incompleteCount); end function out = stringOrEmpty(structValue, fieldName) if isfield(structValue, fieldName) out = string(structValue.(fieldName)); else out = ""; end end function rows = inventoryRowStates(inventory) rows = repmat(struct( ... 'test_file', '', ... 'target', '', ... 'scope', '', ... 'last_run', '', ... 'result_summary', '', ... 'last_run_date', '', ... 'last_run_commit', ''), height(inventory), 1); for idx = 1:height(inventory) rows(idx).test_file = char(inventory.TestFile(idx)); rows(idx).target = char(inventory.Target(idx)); rows(idx).scope = char(inventory.Scope(idx)); rows(idx).last_run = char(inventory.LastRun(idx)); rows(idx).result_summary = char(inventory.ResultSummary(idx)); rows(idx).last_run_date = char(inventory.LastRunDate(idx)); rows(idx).last_run_commit = char(inventory.LastRunCommit(idx)); end end function entries = resultEntries(results) entries = repmat(struct( ... 'name', '', ... 'passed', false, ... 'failed', false, ... 'incomplete', false, ... 'duration_seconds', 0), numel(results), 1); for idx = 1:numel(results) entries(idx).name = char(string(results(idx).Name)); entries(idx).passed = results(idx).Passed; entries(idx).failed = results(idx).Failed; entries(idx).incomplete = results(idx).Incomplete; entries(idx).duration_seconds = durationToSeconds(results(idx).Duration); end end function gitInfo = collectGitMetadata(repoRoot) gitInfo = struct(); gitInfo.commit = strtrim(runCommand(sprintf('git -C "%s" rev-parse HEAD', repoRoot))); gitInfo.short_commit = strtrim(runCommand(sprintf('git -C "%s" rev-parse --short HEAD', repoRoot))); gitInfo.branch = strtrim(runCommand(sprintf('git -C "%s" rev-parse --abbrev-ref HEAD', repoRoot))); statusOutput = runCommand(sprintf('git -C "%s" status --porcelain', repoRoot)); gitInfo.dirty = ~isempty(strtrim(statusOutput)); end function output = runCommand(commandText) [status, output] = system(commandText); if status ~= 0 output = ''; end end function writeTextFile(filePath, content) fid = fopen(filePath, 'w'); assert(fid ~= -1, 'Could not write file: %s', filePath); cleaner = onCleanup(@() fclose(fid)); %#ok fprintf(fid, '%s', content); end function label = sanitizeFileLabel(labelText) label = regexprep(char(string(labelText)), '[^A-Za-z0-9]+', '_'); label = regexprep(label, '^_+|_+$', ''); if isempty(label) label = 'run'; end end function value = durationToSeconds(durationValue) try value = seconds(durationValue); if isa(value, 'duration') value = str2double(char(value)); end catch try value = posixtime(datetime(durationValue) - datetime(0, 0, 0)); catch durationText = char(string(durationValue)); tokens = regexp(durationText, '(\d+):(\d+):([\d\.]+)', 'tokens', 'once'); if isempty(tokens) value = NaN; else value = str2double(tokens{1}) * 3600 + ... str2double(tokens{2}) * 60 + ... str2double(tokens{3}); end end end if ~isnumeric(value) || ~isscalar(value) value = NaN; end end