diff --git a/.github/workflows/bat.yml b/.github/workflows/bat.yml
index b54564f..cc5fdf2 100644
--- a/.github/workflows/bat.yml
+++ b/.github/workflows/bat.yml
@@ -1,35 +1,34 @@
-name: Build and Test
-on: [push]
-permissions:
- contents: read
-
-jobs:
- plugin-tests:
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- include:
- - os: ubuntu-latest
- - os: windows-latest
- - os: macos-latest
- steps:
- - uses: actions/checkout@v6
- - uses: matlab-actions/setup-matlab@v2
- with:
- release: latest-including-prerelease
- - uses: matlab-actions/run-tests@v2
- with:
- source-folder: plugins
-
- bat:
- name: Build and Test
- runs-on: ubuntu-22.04
- steps:
- - uses: actions/checkout@v6
- - uses: actions/setup-node@v6
- with:
- node-version: 24
- - name: Perform npm tasks
- run: npm run ci
-
+name: Build and Test
+on: [push]
+permissions:
+ contents: read
+
+jobs:
+ plugin-tests:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ - os: windows-latest
+ - os: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: matlab-actions/setup-matlab@v3
+ with:
+ release: latest-including-prerelease
+ - uses: matlab-actions/run-tests@v3
+ with:
+ source-folder: plugins
+
+ bat:
+ name: Build and Test
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ - name: Perform npm tasks
+ run: npm run ci
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 2b73fcd..9683fc6 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,77 +1,77 @@
-name: Publish
-on:
- release:
- types: published
-permissions:
- contents: write
-
-jobs:
- build:
- name: Build
- runs-on: ubuntu-latest
- outputs:
- tag: ${{ steps.update-package-version.outputs.version }}
- steps:
- - uses: actions/checkout@v6
- - name: Configure git
- run: |
- git config user.name 'Release Action'
- git config user.email '<>'
- - uses: actions/setup-node@v6
- with:
- node-version: 24
-
- # Call `npm version`. It increments the version and commits the changes.
- # We'll save the output (new version string) for use in the following
- # steps
- - name: Update package version
- id: update-package-version
- run: |
- git tag -d "${{ github.event.release.tag_name }}"
- VERSION=$(npm version "${{ github.event.release.tag_name }}" --no-git-tag-version)
- git add package.json package-lock.json
- git commit -m "[skip ci] Bump $VERSION"
- git push origin HEAD:main
-
- # Now carry on, business as usual
- - name: Perform npm tasks
- run: npm run ci
-
- # Finally, create a detached commit containing the built artifacts and tag
- # it with the release. Note: the fact that the branch is locally updated
- # will not be relayed (pushed) to origin
- - name: Commit to release branch
- id: release_info
- run: |
- # Check for semantic versioning
- longVersion="${{github.event.release.tag_name}}"
- echo "Preparing release for version $longVersion"
- [[ $longVersion == v[0-9]*.[0-9]*.[0-9]* ]] || (echo "must follow semantic versioning" && exit 1)
- majorVersion=$(echo ${longVersion%.*.*})
- minorVersion=$(echo ${longVersion%.*})
-
- # Add the built artifacts. Using --force because dist/lib should be in
- # .gitignore
- git add --force dist lib
-
- # Make the commit
- MESSAGE="Build for $(git rev-parse --short HEAD)"
- git commit --allow-empty -m "$MESSAGE"
- git tag -f -a -m "Release $longVersion" $longVersion
-
- # Get the commit of the tag you just released
- commitHash=$(git rev-list -n 1 $longVersion)
-
- # Delete the old major and minor version tags locally
- git tag -d $majorVersion || true
- git tag -d $minorVersion || true
-
- # Make new major and minor version tags locally that point to the commit you got from the "git rev-list" above
- git tag -f $majorVersion $commitHash
- git tag -f $minorVersion $commitHash
-
- # Force push the new minor version tag to overwrite the old tag remotely
- echo "Pushing new tags"
- git push -f origin $longVersion
- git push -f origin $majorVersion
- git push -f origin $minorVersion
+name: Publish
+on:
+ release:
+ types: published
+permissions:
+ contents: write
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ outputs:
+ tag: ${{ steps.update-package-version.outputs.version }}
+ steps:
+ - uses: actions/checkout@v6
+ - name: Configure git
+ run: |
+ git config user.name 'Release Action'
+ git config user.email '<>'
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+
+ # Call `npm version`. It increments the version and commits the changes.
+ # We'll save the output (new version string) for use in the following
+ # steps
+ - name: Update package version
+ id: update-package-version
+ run: |
+ git tag -d "${{ github.event.release.tag_name }}"
+ VERSION=$(npm version "${{ github.event.release.tag_name }}" --no-git-tag-version)
+ git add package.json package-lock.json
+ git commit -m "[skip ci] Bump $VERSION"
+ git push origin HEAD:main
+
+ # Now carry on, business as usual
+ - name: Perform npm tasks
+ run: npm run ci
+
+ # Finally, create a detached commit containing the built artifacts and tag
+ # it with the release. Note: the fact that the branch is locally updated
+ # will not be relayed (pushed) to origin
+ - name: Commit to release branch
+ id: release_info
+ run: |
+ # Check for semantic versioning
+ longVersion="${{github.event.release.tag_name}}"
+ echo "Preparing release for version $longVersion"
+ [[ $longVersion == v[0-9]*.[0-9]*.[0-9]* ]] || (echo "must follow semantic versioning" && exit 1)
+ majorVersion=$(echo ${longVersion%.*.*})
+ minorVersion=$(echo ${longVersion%.*})
+
+ # Add the built artifacts. Using --force because dist/lib should be in
+ # .gitignore
+ git add --force dist lib
+
+ # Make the commit
+ MESSAGE="Build for $(git rev-parse --short HEAD)"
+ git commit --allow-empty -m "$MESSAGE"
+ git tag -f -a -m "Release $longVersion" $longVersion
+
+ # Get the commit of the tag you just released
+ commitHash=$(git rev-list -n 1 $longVersion)
+
+ # Delete the old major and minor version tags locally
+ git tag -d $majorVersion || true
+ git tag -d $minorVersion || true
+
+ # Make new major and minor version tags locally that point to the commit you got from the "git rev-list" above
+ git tag -f $majorVersion $commitHash
+ git tag -f $minorVersion $commitHash
+
+ # Force push the new minor version tag to overwrite the old tag remotely
+ echo "Pushing new tags"
+ git push -f origin $longVersion
+ git push -f origin $majorVersion
+ git push -f origin $minorVersion
diff --git a/devel/contributing.md b/devel/contributing.md
new file mode 100644
index 0000000..018b52c
--- /dev/null
+++ b/devel/contributing.md
@@ -0,0 +1,44 @@
+## Contributing
+
+Verify changes by running tests and building locally with the following command:
+
+```
+npm run ci
+```
+
+## Creating a New Release
+
+Familiarize yourself with the best practices for [releasing and maintaining GitHub actions](https://docs.github.com/en/actions/creating-actions/releasing-and-maintaining-actions).
+
+Changes should be made on a new branch. The new branch should be merged to the main branch via a pull request. Ensure that all of the CI pipeline checks and tests have passed for your changes.
+
+After the pull request has been approved and merged to main, follow the Github process for [creating a new release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository). The release must follow semantic versioning (ex: vX.Y.Z). This will kick off a new pipeline execution, and the action will automatically be published to the GitHub Actions Marketplace if the pipeline finishes successfully. Check the [GitHub Marketplace](https://github.com/marketplace/actions/setup-matlab) and check the major version in the repository (ex: v1 for v1.0.0) to ensure that the new semantically versioned tag is available.
+
+## Adding a Pre-Commit Hook
+
+You can run all CI checks before each commit by adding a pre-commit hook. To do so, navigate to the repository root folder and run the following commands:
+
+_bash (Linux/macOS)_
+
+```sh
+echo '#!/bin/sh' > .git/hooks/pre-commit
+echo 'npm run ci' >> .git/hooks/pre-commit
+chmod +x .git/hooks/pre-commit
+```
+
+_Command Prompt (Windows)_
+
+```cmd
+echo #!/bin/sh > .git\hooks\pre-commit
+echo npm run ci >> .git\hooks\pre-commit
+```
+
+_PowerShell (Windows)_
+
+```pwsh
+Set-Content .git\hooks\pre-commit '#!/bin/sh'
+Add-Content .git\hooks\pre-commit 'npm run ci'
+```
+
+> **Note:**
+> Git hooks are not version-controlled, so you need to set up this hook for each fresh clone of the repository.
diff --git a/jest.config.ts b/jest.config.ts
index 5551fb3..864e6f1 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -14,7 +14,7 @@ export default {
},
],
},
- extensionsToTreatAsEsm: ['.ts'],
+ extensionsToTreatAsEsm: [".ts"],
transformIgnorePatterns: ["node_modules/(?!(@actions)/)"],
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1",
diff --git a/package-lock.json b/package-lock.json
index 49a5c30..114d28c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1956,9 +1956,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
+ "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4559,9 +4559,9 @@
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
+ "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index ecaf282..325a6c0 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"prepare": "npm run build",
"package": "ncc build --minify",
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
- "all": "npm test && npm run build && npm run package",
+ "all": "npm run format-check && npm test && npm run build && npm run package",
"ci": "npm run clean && npm ci --ignore-scripts && npm run all"
},
"files": [
diff --git a/src/buildSummary.ts b/src/buildSummary.ts
index 30803b4..02d7530 100644
--- a/src/buildSummary.ts
+++ b/src/buildSummary.ts
@@ -1,7 +1,7 @@
// Copyright 2024-25 The MathWorks, Inc.
import * as core from "@actions/core";
-import { join } from 'path';
-import { readFileSync, unlinkSync, existsSync } from 'fs';
+import { join } from "path";
+import { readFileSync, unlinkSync, existsSync } from "fs";
export function addSummary(taskSummaryTableRows: string[][], actionName: string) {
try {
@@ -9,25 +9,30 @@ export function addSummary(taskSummaryTableRows: string[][], actionName: string)
.addHeading("MATLAB Build Results (" + actionName + ") ")
.addTable(taskSummaryTableRows);
} catch (e) {
- console.error('An error occurred while adding the build results table to the summary:', e);
+ console.error("An error occurred while adding the build results table to the summary:", e);
}
}
export function getSummaryRows(buildSummary: string): any[] {
const rows = JSON.parse(buildSummary).map((t: any) => {
if (t.failed) {
- return [t.name, 'đ´ Failed', t.description, t.duration];
+ return [t.name, "đ´ Failed", t.description, t.duration];
} else if (t.skipped) {
- return [t.name, 'đĩ Skipped' + ' (' + interpretSkipReason(t.skipReason) + ')', t.description, t.duration];
+ return [
+ t.name,
+ "đĩ Skipped" + " (" + interpretSkipReason(t.skipReason) + ")",
+ t.description,
+ t.duration,
+ ];
} else {
- return [t.name, 'đĸ Successful', t.description, t.duration];
+ return [t.name, "đĸ Successful", t.description, t.duration];
}
});
return rows;
}
-export function interpretSkipReason(skipReason: string){
- switch(skipReason) {
+export function interpretSkipReason(skipReason: string) {
+ switch (skipReason) {
case "UpToDate":
return "up-to-date";
case "UserSpecified":
@@ -40,28 +45,32 @@ export function interpretSkipReason(skipReason: string){
}
}
-export function processAndAddBuildSummary(
- runnerTemp: string,
- runId: string,
- actionName: string
-) {
- const header = [{ data: 'MATLAB Task', header: true }, { data: 'Status', header: true }, { data: 'Description', header: true }, { data: 'Duration (HH:mm:ss)', header: true }];
+export function processAndAddBuildSummary(runnerTemp: string, runId: string, actionName: string) {
+ const header = [
+ { data: "MATLAB Task", header: true },
+ { data: "Status", header: true },
+ { data: "Description", header: true },
+ { data: "Duration (HH:mm:ss)", header: true },
+ ];
const filePath: string = join(runnerTemp, `buildSummary${runId}.json`);
let taskSummaryTable;
if (existsSync(filePath)) {
try {
- const buildSummary = readFileSync(filePath, { encoding: 'utf8' });
+ const buildSummary = readFileSync(filePath, { encoding: "utf8" });
const rows = getSummaryRows(buildSummary);
taskSummaryTable = [header, ...rows];
} catch (e) {
- console.error('An error occurred while reading the build summary file:', e);
+ console.error("An error occurred while reading the build summary file:", e);
return;
} finally {
try {
unlinkSync(filePath);
} catch (e) {
- console.error(`An error occurred while trying to delete the build summary file ${filePath}:`, e);
+ console.error(
+ `An error occurred while trying to delete the build summary file ${filePath}:`,
+ e,
+ );
}
}
addSummary(taskSummaryTable, actionName);
diff --git a/src/buildSummary.unit.test.ts b/src/buildSummary.unit.test.ts
index d8454bb..d6d80cd 100644
--- a/src/buildSummary.unit.test.ts
+++ b/src/buildSummary.unit.test.ts
@@ -2,7 +2,7 @@
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
-jest.unstable_mockModule('@actions/core', () => ({
+jest.unstable_mockModule("@actions/core", () => ({
summary: {
addTable: jest.fn().mockReturnThis(),
addHeading: jest.fn().mockReturnThis(),
@@ -19,33 +19,66 @@ beforeEach(() => {
(core.summary.write as jest.Mock).mockReturnThis();
});
-describe('summaryGeneration', () => {
- it('should process and return summary rows for valid JSON with different task statuses', () => {
+describe("summaryGeneration", () => {
+ it("should process and return summary rows for valid JSON with different task statuses", () => {
const mockBuildSummary = JSON.stringify([
- { name: 'Task 1', failed: true, skipped: false, description: 'Task 1 description', duration: '00:00:10' },
- { name: 'Task 2', failed: false, skipped: true, skipReason: 'UserSpecified', description: 'Task 2 description', duration: '00:00:20' },
- { name: 'Task 3', failed: false, skipped: true, skipReason: 'DependencyFailed', description: 'Task 3 description', duration: '00:00:20' },
- { name: 'Task 4', failed: false, skipped: true, skipReason: 'UpToDate', description: 'Task 4 description', duration: '00:00:20' },
- { name: 'Task 5', failed: false, skipped: false, description: 'Task 5 description', duration: '00:00:30' }
+ {
+ name: "Task 1",
+ failed: true,
+ skipped: false,
+ description: "Task 1 description",
+ duration: "00:00:10",
+ },
+ {
+ name: "Task 2",
+ failed: false,
+ skipped: true,
+ skipReason: "UserSpecified",
+ description: "Task 2 description",
+ duration: "00:00:20",
+ },
+ {
+ name: "Task 3",
+ failed: false,
+ skipped: true,
+ skipReason: "DependencyFailed",
+ description: "Task 3 description",
+ duration: "00:00:20",
+ },
+ {
+ name: "Task 4",
+ failed: false,
+ skipped: true,
+ skipReason: "UpToDate",
+ description: "Task 4 description",
+ duration: "00:00:20",
+ },
+ {
+ name: "Task 5",
+ failed: false,
+ skipped: false,
+ description: "Task 5 description",
+ duration: "00:00:30",
+ },
]);
const result = buildSummary.getSummaryRows(mockBuildSummary);
expect(result).toEqual([
- ['Task 1', 'đ´ Failed', 'Task 1 description', '00:00:10'],
- ['Task 2', 'đĩ Skipped (user requested)', 'Task 2 description', '00:00:20'],
- ['Task 3', 'đĩ Skipped (dependency failed)', 'Task 3 description', '00:00:20'],
- ['Task 4', 'đĩ Skipped (up-to-date)', 'Task 4 description', '00:00:20'],
- ['Task 5', 'đĸ Successful', 'Task 5 description', '00:00:30']
+ ["Task 1", "đ´ Failed", "Task 1 description", "00:00:10"],
+ ["Task 2", "đĩ Skipped (user requested)", "Task 2 description", "00:00:20"],
+ ["Task 3", "đĩ Skipped (dependency failed)", "Task 3 description", "00:00:20"],
+ ["Task 4", "đĩ Skipped (up-to-date)", "Task 4 description", "00:00:20"],
+ ["Task 5", "đĸ Successful", "Task 5 description", "00:00:30"],
]);
});
- it('writes the summary correctly', () => {
+ it("writes the summary correctly", () => {
const mockTableRows = [
- ['MATLAB Task', 'Status', 'Description', 'Duration (HH:mm:ss)'],
- ['Test Task', 'đ´ Failed', 'A test task', '00:00:10'],
+ ["MATLAB Task", "Status", "Description", "Duration (HH:mm:ss)"],
+ ["Test Task", "đ´ Failed", "A test task", "00:00:10"],
];
- const actionName = 'run-build';
+ const actionName = "run-build";
buildSummary.addSummary(mockTableRows, actionName);
diff --git a/src/codeCoverageSummary.ts b/src/codeCoverageSummary.ts
index b33300d..6193fec 100644
--- a/src/codeCoverageSummary.ts
+++ b/src/codeCoverageSummary.ts
@@ -11,26 +11,23 @@ interface CoverageMetric {
export interface CoverageData {
MetricLevel?: string;
- FunctionCoverage?: CoverageMetric;
- StatementCoverage?: CoverageMetric;
- DecisionCoverage?: CoverageMetric;
- ConditionCoverage?: CoverageMetric;
- MCDCCoverage?: CoverageMetric;
+ FunctionCoverage?: CoverageMetric;
+ StatementCoverage?: CoverageMetric;
+ DecisionCoverage?: CoverageMetric;
+ ConditionCoverage?: CoverageMetric;
+ MCDCCoverage?: CoverageMetric;
}
-export function getCoverageResults(
- runnerTemp: string,
- runId: string,
-): CoverageData | null {
+export function getCoverageResults(runnerTemp: string, runId: string): CoverageData | null {
let coverageData = null;
const coveragePath = path.join(runnerTemp, `matlabCoverageResults${runId}.json`);
-
+
if (existsSync(coveragePath)) {
try {
const coverageArray: CoverageData[] = JSON.parse(readFileSync(coveragePath, "utf8"));
if (coverageArray.length !== 0) {
coverageData = coverageArray[coverageArray.length - 1];
- }
+ }
} catch (e) {
console.error(
`An error occurred while reading the code coverage summary file ${coveragePath}:`,
@@ -52,42 +49,41 @@ export function getCoverageResults(
function formatPercentage(percentage: number): string {
if (percentage === null || percentage === undefined || isNaN(percentage)) {
- return '0.00%';
+ return "0.00%";
}
- return percentage.toFixed(2) + '%';
+ return percentage.toFixed(2) + "%";
}
export function getCoverageTable(coverage: CoverageData): string {
-
// Define all possible columns
const allColumns = [
- { name: 'Function', data: coverage.FunctionCoverage },
- { name: 'Statement', data: coverage.StatementCoverage },
- { name: 'Decision', data: coverage.DecisionCoverage },
- { name: 'Condition', data: coverage.ConditionCoverage },
- { name: 'MC/DC', data: coverage.MCDCCoverage }
+ { name: "Function", data: coverage.FunctionCoverage },
+ { name: "Statement", data: coverage.StatementCoverage },
+ { name: "Decision", data: coverage.DecisionCoverage },
+ { name: "Condition", data: coverage.ConditionCoverage },
+ { name: "MC/DC", data: coverage.MCDCCoverage },
];
// Filter to only include columns where data actually exists
- const visibleColumns = allColumns.filter(col => col.data !== undefined && col.data !== null);
+ const visibleColumns = allColumns.filter((col) => col.data !== undefined && col.data !== null);
// Build header row
- const headers = visibleColumns.map(col => `
${col.name} | `).join('');
+ const headers = visibleColumns.map((col) => `${col.name} | `).join("");
const headerRow = `| Metric | ${headers}
`;
// Build percentage row
- const percentages = visibleColumns.map(col =>
- `${formatPercentage(col.data!.Percentage)} | `
- ).join('');
+ const percentages = visibleColumns
+ .map((col) => `${formatPercentage(col.data!.Percentage)} | `)
+ .join("");
const percentageRow = `| Percentage | ${percentages}
`;
// Build covered/total row
- const coveredTotals = visibleColumns.map(col =>
- `${col.data!.Executed}/${col.data!.Total} | `
- ).join('');
+ const coveredTotals = visibleColumns
+ .map((col) => `${col.data!.Executed}/${col.data!.Total} | `)
+ .join("");
const coveredTotalRow = `| Covered/Total | ${coveredTotals}
`;
const tableHTML = `${headerRow}${percentageRow}${coveredTotalRow}
`;
-
+
return tableHTML;
}
diff --git a/src/codeCoverageSummary.unit.test.ts b/src/codeCoverageSummary.unit.test.ts
index 116b2c6..d250f9f 100644
--- a/src/codeCoverageSummary.unit.test.ts
+++ b/src/codeCoverageSummary.unit.test.ts
@@ -36,7 +36,7 @@ describe("Coverage Data Retrieval Tests", () => {
expect(result).toBeNull();
expect(fs.existsSync).toHaveBeenCalledWith(
- path.join(runnerTemp, `matlabCoverageResults${runId}.json`)
+ path.join(runnerTemp, `matlabCoverageResults${runId}.json`),
);
expect(mockUnlinkSync).not.toHaveBeenCalled();
});
@@ -48,14 +48,14 @@ describe("Coverage Data Retrieval Tests", () => {
StatementCoverage: {
Executed: 80,
Total: 100,
- Percentage: 80.0
+ Percentage: 80.0,
},
FunctionCoverage: {
Executed: 15,
Total: 20,
- Percentage: 75.0
- }
- }
+ Percentage: 75.0,
+ },
+ },
];
(fs.existsSync as jest.Mock).mockReturnValue(true);
@@ -66,10 +66,10 @@ describe("Coverage Data Retrieval Tests", () => {
expect(result).toEqual(mockCoverageData[0]);
expect(fs.readFileSync).toHaveBeenCalledWith(
path.join(runnerTemp, `matlabCoverageResults${runId}.json`),
- "utf8"
+ "utf8",
);
expect(mockUnlinkSync).toHaveBeenCalledWith(
- path.join(runnerTemp, `matlabCoverageResults${runId}.json`)
+ path.join(runnerTemp, `matlabCoverageResults${runId}.json`),
);
});
@@ -80,17 +80,17 @@ describe("Coverage Data Retrieval Tests", () => {
StatementCoverage: {
Executed: 70,
Total: 100,
- Percentage: 70.0
- }
+ Percentage: 70.0,
+ },
},
{
MetricLevel: "decision",
DecisionCoverage: {
Executed: 90,
Total: 100,
- Percentage: 90.0
- }
- }
+ Percentage: 90.0,
+ },
+ },
];
(fs.existsSync as jest.Mock).mockReturnValue(true);
@@ -120,7 +120,9 @@ describe("Coverage Data Retrieval Tests", () => {
expect(result).toBeNull();
expect(consoleSpy).toHaveBeenCalledWith(
- expect.stringContaining("An error occurred while reading the code coverage summary file"),
+ expect.stringContaining(
+ "An error occurred while reading the code coverage summary file",
+ ),
expect.anything(),
);
expect(mockUnlinkSync).toHaveBeenCalled();
@@ -136,7 +138,9 @@ describe("Coverage Data Retrieval Tests", () => {
expect(result).toBeNull();
expect(consoleSpy).toHaveBeenCalledWith(
- expect.stringContaining("An error occurred while reading the code coverage summary file"),
+ expect.stringContaining(
+ "An error occurred while reading the code coverage summary file",
+ ),
expect.anything(),
);
expect(mockUnlinkSync).toHaveBeenCalled();
@@ -144,13 +148,17 @@ describe("Coverage Data Retrieval Tests", () => {
it("should handle file deletion errors gracefully", () => {
(fs.existsSync as jest.Mock).mockReturnValue(true);
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify([{
- StatementCoverage: {
- Executed: 80,
- Total: 100,
- Percentage: 80.0
- }
- }]));
+ (fs.readFileSync as jest.Mock).mockReturnValue(
+ JSON.stringify([
+ {
+ StatementCoverage: {
+ Executed: 80,
+ Total: 100,
+ Percentage: 80.0,
+ },
+ },
+ ]),
+ );
mockUnlinkSync.mockImplementationOnce(() => {
throw new Error("Permission denied - cannot delete file");
});
@@ -159,7 +167,9 @@ describe("Coverage Data Retrieval Tests", () => {
expect(result).toBeDefined();
expect(consoleSpy).toHaveBeenCalledWith(
- expect.stringContaining("An error occurred while trying to delete the code coverage summary file"),
+ expect.stringContaining(
+ "An error occurred while trying to delete the code coverage summary file",
+ ),
expect.anything(),
);
expect(mockUnlinkSync).toHaveBeenCalled();
@@ -177,28 +187,28 @@ describe("Coverage Table HTML Generation Tests", () => {
FunctionCoverage: {
Executed: 15,
Total: 20,
- Percentage: 75.0
+ Percentage: 75.0,
},
StatementCoverage: {
Executed: 80,
Total: 100,
- Percentage: 80.55
+ Percentage: 80.55,
},
DecisionCoverage: {
Executed: 45,
Total: 50,
- Percentage: 90.0
+ Percentage: 90.0,
},
ConditionCoverage: {
Executed: 30,
Total: 40,
- Percentage: 75.0
+ Percentage: 75.0,
},
MCDCCoverage: {
Executed: 20,
Total: 25,
- Percentage: 80.0
- }
+ Percentage: 80.0,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -249,8 +259,8 @@ describe("Coverage Table HTML Generation Tests", () => {
StatementCoverage: {
Executed: 85,
Total: 100,
- Percentage: 85.0
- }
+ Percentage: 85.0,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -282,18 +292,18 @@ describe("Coverage Table HTML Generation Tests", () => {
StatementCoverage: {
Executed: 80,
Total: 100,
- Percentage: 80.0
+ Percentage: 80.0,
},
DecisionCoverage: {
Executed: 45,
Total: 50,
- Percentage: 90.0
+ Percentage: 90.0,
},
MCDCCoverage: {
Executed: 20,
Total: 25,
- Percentage: 80.0
- }
+ Percentage: 80.0,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -322,8 +332,8 @@ describe("Coverage Table HTML Generation Tests", () => {
StatementCoverage: {
Executed: 0,
Total: 100,
- Percentage: 0
- }
+ Percentage: 0,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -336,8 +346,8 @@ describe("Coverage Table HTML Generation Tests", () => {
StatementCoverage: {
Executed: 100,
Total: 100,
- Percentage: 100
- }
+ Percentage: 100,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -350,8 +360,8 @@ describe("Coverage Table HTML Generation Tests", () => {
StatementCoverage: {
Executed: 333,
Total: 1000,
- Percentage: 33.333333
- }
+ Percentage: 33.333333,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -366,8 +376,8 @@ describe("HTML Structure and Alignment Tests", () => {
StatementCoverage: {
Executed: 80,
Total: 100,
- Percentage: 80.0
- }
+ Percentage: 80.0,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -376,7 +386,7 @@ describe("HTML Structure and Alignment Tests", () => {
const document = dom.window.document;
const rows = document.querySelectorAll("tr");
- rows.forEach(row => {
+ rows.forEach((row) => {
expect(row.getAttribute("align")).toBe("center");
});
});
@@ -386,13 +396,13 @@ describe("HTML Structure and Alignment Tests", () => {
StatementCoverage: {
Executed: 80,
Total: 100,
- Percentage: 80.0
+ Percentage: 80.0,
},
FunctionCoverage: {
Executed: 15,
Total: 20,
- Percentage: 75.0
- }
+ Percentage: 75.0,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -410,8 +420,8 @@ describe("Edge Cases and Special Values", () => {
StatementCoverage: {
Executed: 0,
Total: 0,
- Percentage: NaN
- }
+ Percentage: NaN,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -424,8 +434,8 @@ describe("Edge Cases and Special Values", () => {
StatementCoverage: {
Executed: 0,
Total: 0,
- Percentage: undefined as any
- }
+ Percentage: undefined as any,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -438,8 +448,8 @@ describe("Edge Cases and Special Values", () => {
StatementCoverage: {
Executed: 0,
Total: 0,
- Percentage: null as any
- }
+ Percentage: null as any,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
@@ -452,8 +462,8 @@ describe("Edge Cases and Special Values", () => {
StatementCoverage: {
Executed: 999999,
Total: 1000000,
- Percentage: 99.9999
- }
+ Percentage: 99.9999,
+ },
};
const html = codeCoverageSummary.getCoverageTable(mockCoverageData);
diff --git a/src/matlab.ts b/src/matlab.ts
index 02dd1fe..f2403e0 100644
--- a/src/matlab.ts
+++ b/src/matlab.ts
@@ -37,9 +37,9 @@ export async function generateScript(workspaceDir: string, command: string): Pro
encoding: "utf8",
});
- return {
- dir: temporaryDirectory,
- command: scriptName
+ return {
+ dir: temporaryDirectory,
+ command: scriptName,
};
}
@@ -53,7 +53,13 @@ export async function generateScript(workspaceDir: string, command: string): Pro
* @param architecture Architecture of the runner (e.g., "x64")
* @param fn ExecFn that will execute a command on the runner
*/
-export async function runCommand(hs: HelperScript, platform: string, architecture: string, fn: ExecFn, args?: string[]): Promise {
+export async function runCommand(
+ hs: HelperScript,
+ platform: string,
+ architecture: string,
+ fn: ExecFn,
+ args?: string[],
+): Promise {
const rmcPath = getRunMATLABCommandScriptPath(platform, architecture);
await fs.chmod(rmcPath, 0o777);
@@ -62,7 +68,7 @@ export async function runCommand(hs: HelperScript, platform: string, architectur
let execArgs = [rmcArg];
if (args) {
- execArgs = execArgs.concat(args);
+ execArgs = execArgs.concat(args);
}
const exitCode = await fn(rmcPath, execArgs);
@@ -76,10 +82,12 @@ export async function runCommand(hs: HelperScript, platform: string, architectur
*
* @param platform Operating system of the runner (e.g., "win32" or "linux")
* @param architecture Architecture of the runner (e.g., "x64")
-*/
+ */
export function getRunMATLABCommandScriptPath(platform: string, architecture: string): string {
if (architecture != "x64" && !(platform == "darwin" && architecture == "arm64")) {
- throw new Error(`This action is not supported on ${platform} runners using the ${architecture} architecture.`);
+ throw new Error(
+ `This action is not supported on ${platform} runners using the ${architecture} architecture.`,
+ );
}
let ext;
let platformDir;
@@ -101,9 +109,10 @@ export function getRunMATLABCommandScriptPath(platform: string, architecture: st
platformDir = "glnxa64";
break;
default:
- throw new Error(`This action is not supported on ${platform} runners using the ${architecture} architecture.`);
+ throw new Error(
+ `This action is not supported on ${platform} runners using the ${architecture} architecture.`,
+ );
}
const rmcPath = path.join(import.meta.dirname, "bin", platformDir, `run-matlab-command${ext}`);
return rmcPath;
-
}
diff --git a/src/matlab.unit.test.ts b/src/matlab.unit.test.ts
index 3bca266..93d891a 100644
--- a/src/matlab.unit.test.ts
+++ b/src/matlab.unit.test.ts
@@ -60,7 +60,7 @@ describe("script generation", () => {
describe("run command", () => {
const helperScript = { dir: "/home/sweet/home", command: "disp('hello, world');" };
const platform = "win32";
- const architecture = "x64"
+ const architecture = "x64";
it("ideally works", async () => {
const chmod = jest.spyOn(fs, "chmod");
@@ -80,7 +80,11 @@ describe("run command", () => {
chmod.mockResolvedValue(undefined);
execFn.mockResolvedValue(0);
- const actual = matlab.runCommand(helperScript, platform, architecture, execFn, ["-nojvm", "-logfile", "file"]);
+ const actual = matlab.runCommand(helperScript, platform, architecture, execFn, [
+ "-nojvm",
+ "-logfile",
+ "file",
+ ]);
await expect(actual).resolves.toBeUndefined();
expect(execFn.mock.calls[0][1]![1]).toBe("-nojvm");
expect(execFn.mock.calls[0][1]![2]).toBe("-logfile");
@@ -127,8 +131,8 @@ describe("run command", () => {
});
describe("ci helper path", () => {
- const platform = "linux"
- const architecture = "x64"
+ const platform = "linux";
+ const architecture = "x64";
const testExtension = (platform: string, ext: string) => {
it(`considers the appropriate script on ${platform}`, () => {
const actualPath = matlab.getRunMATLABCommandScriptPath(platform, architecture);
@@ -143,7 +147,7 @@ describe("ci helper path", () => {
expect(actualPath).toContain(subdirectory);
});
};
-
+
testExtension("win32", "exe");
testExtension("darwin", "");
testExtension("linux", "");
@@ -154,10 +158,10 @@ describe("ci helper path", () => {
testDirectory("linux", "x64", "glnxa64");
it("errors on unsupported platform", () => {
- expect(() => matlab.getRunMATLABCommandScriptPath('sunos',architecture)).toThrow();
- })
+ expect(() => matlab.getRunMATLABCommandScriptPath("sunos", architecture)).toThrow();
+ });
it("errors on unsupported architecture", () => {
- expect(() => matlab.getRunMATLABCommandScriptPath(platform, 'x86')).toThrow();
- })
+ expect(() => matlab.getRunMATLABCommandScriptPath(platform, "x86")).toThrow();
+ });
});
diff --git a/src/script.ts b/src/script.ts
index 5b3e185..78d10d7 100644
--- a/src/script.ts
+++ b/src/script.ts
@@ -10,10 +10,8 @@ import * as path from "path";
* @returns MATLAB command.
*/
export function prepare(command: string): string {
- const pluginsPath = path.join(import.meta.dirname, "plugins").replaceAll("'","''");
- return `cd(getenv('MW_ORIG_WORKING_FOLDER')); ` +
- `addpath('` + pluginsPath + `'); `
- + command;
+ const pluginsPath = path.join(import.meta.dirname, "plugins").replaceAll("'", "''");
+ return `cd(getenv('MW_ORIG_WORKING_FOLDER')); ` + `addpath('` + pluginsPath + `'); ` + command;
}
/**
diff --git a/src/script.unit.test.ts b/src/script.unit.test.ts
index 79fe7f8..1c8e4a0 100644
--- a/src/script.unit.test.ts
+++ b/src/script.unit.test.ts
@@ -8,8 +8,9 @@ describe("call generation", () => {
it("ideally works", () => {
// I know what your thinking
const testCommand = "disp('hello world')";
- const pluginsPath = path.join(import.meta.dirname, "plugins").replaceAll("'","''");
- const expectedString = `cd(getenv('MW_ORIG_WORKING_FOLDER')); addpath('` + pluginsPath + `'); ${testCommand}`;
+ const pluginsPath = path.join(import.meta.dirname, "plugins").replaceAll("'", "''");
+ const expectedString =
+ `cd(getenv('MW_ORIG_WORKING_FOLDER')); addpath('` + pluginsPath + `'); ${testCommand}`;
expect(script.prepare(testCommand)).toMatch(expectedString);
});
diff --git a/src/testResultsSummary.ts b/src/testResultsSummary.ts
index 714efa8..a835b19 100644
--- a/src/testResultsSummary.ts
+++ b/src/testResultsSummary.ts
@@ -70,7 +70,7 @@ export function processAndAddTestSummary(
) {
const testResultsData = getTestResults(runnerTemp, runId, workspace);
const coverageResultsData = getCoverageResults(runnerTemp, runId);
- if(testResultsData || coverageResultsData) {
+ if (testResultsData || coverageResultsData) {
addSummary(testResultsData, coverageResultsData, actionName);
}
}
@@ -143,8 +143,8 @@ export function addSummary(
// Add test results table if available
if (testResultsData) {
const helpLink =
- `âšī¸`;
+ `âšī¸`;
const header = getTestHeader(testResultsData.Stats);
core.summary
@@ -161,9 +161,7 @@ export function addSummary(
// Add detailed test results
if (testResultsData) {
const detailedResults = getDetailedResults(testResultsData.TestResults);
- core.summary
- .addHeading("All tests", 3)
- .addRaw(detailedResults, true);
+ core.summary.addHeading("All tests", 3).addRaw(detailedResults, true);
}
} catch (e) {
console.error("An error occurred while adding the test results to the summary:", e);
@@ -175,19 +173,39 @@ export function getTestHeader(stats: TestStatistics): string {
`
| Total tests |
- Passed ` + getStatusEmoji(MatlabTestStatus.PASSED) + ` |
- Failed ` + getStatusEmoji(MatlabTestStatus.FAILED) + ` |
- Incomplete ` + getStatusEmoji(MatlabTestStatus.INCOMPLETE) + ` |
- Not Run ` + getStatusEmoji(MatlabTestStatus.NOT_RUN) + ` |
+ Passed ` +
+ getStatusEmoji(MatlabTestStatus.PASSED) +
+ ` |
+ Failed ` +
+ getStatusEmoji(MatlabTestStatus.FAILED) +
+ ` |
+ Incomplete ` +
+ getStatusEmoji(MatlabTestStatus.INCOMPLETE) +
+ ` |
+ Not Run ` +
+ getStatusEmoji(MatlabTestStatus.NOT_RUN) +
+ ` |
Duration(s) â |
- | ` + stats.Total + ` |
- ` + stats.Passed + ` |
- ` + stats.Failed + ` |
- ` + stats.Incomplete + ` |
- ` + stats.NotRun + ` |
- ` + stats.Duration.toFixed(2) + ` |
+ ` +
+ stats.Total +
+ ` |
+ ` +
+ stats.Passed +
+ ` |
+ ` +
+ stats.Failed +
+ ` |
+ ` +
+ stats.Incomplete +
+ ` |
+ ` +
+ stats.NotRun +
+ ` |
+ ` +
+ stats.Duration.toFixed(2) +
+ ` |
`
);
@@ -200,10 +218,10 @@ export function getDetailedResults(testResults: MatlabTestFile[][]): string {
Test File |
Duration(s) |
` +
- testResults
- .flat()
- .map((file) => generateTestFileRow(file))
- .join("") +
+ testResults
+ .flat()
+ .map((file) => generateTestFileRow(file))
+ .join("") +
``
);
}
@@ -216,11 +234,17 @@ function generateTestFileRow(file: MatlabTestFile): string {
return (
`
-
+
- ` +
- statusEmoji + ` ` + file.Name +
- `
+ ` +
+ statusEmoji +
+ ` ` +
+ file.Name +
+ `
@@ -229,15 +253,15 @@ function generateTestFileRow(file: MatlabTestFile): string {
| Diagnostics |
Duration(s) |
` +
- file.TestCases.map((tc) => generateTestCaseRow(tc)).join("") +
- `
+ file.TestCases.map((tc) => generateTestCaseRow(tc)).join("") +
+ `
|
` +
- `` +
- file.Duration.toFixed(2) +
- `` +
- ` |
+ `` +
+ file.Duration.toFixed(2) +
+ `` +
+ `
`
);
}
@@ -246,26 +270,32 @@ function generateTestCaseRow(testCase: MatlabTestCase): string {
const statusEmoji = getStatusEmoji(testCase.Status);
const diagnosticsColumn =
testCase.Diagnostics.length > 0
- ? testCase.Diagnostics
- .map(
- (diagnostic) =>
- `` +
- `` +
- diagnostic.Event +
- `
` +
- `` +
- diagnostic.Report.replace(/\n/g, "
").trim() +
- `
` +
- ` `,
- )
- .join("")
+ ? testCase.Diagnostics.map(
+ (diagnostic) =>
+ `` +
+ `` +
+ diagnostic.Event +
+ `
` +
+ `` +
+ diagnostic.Report.replace(/\n/g, "
").trim() +
+ `
` +
+ ` `,
+ ).join("")
: "";
return (
`` +
- `| ` + statusEmoji + ` ` + testCase.Name + ` | ` +
- `` + diagnosticsColumn + ` | ` +
- `` + testCase.Duration.toFixed(2) + ` | ` +
+ `` +
+ statusEmoji +
+ ` ` +
+ testCase.Name +
+ ` | ` +
+ `` +
+ diagnosticsColumn +
+ ` | ` +
+ `` +
+ testCase.Duration.toFixed(2) +
+ ` | ` +
`
`
);
}
@@ -337,7 +367,9 @@ function determineTestStatus(testResult: MatlabTestResultJson): MatlabTestStatus
}
}
-function processDiagnostics(diagnostics: MatlabTestDiagnostics | MatlabTestDiagnostics[] | undefined): MatlabTestDiagnostics[] {
+function processDiagnostics(
+ diagnostics: MatlabTestDiagnostics | MatlabTestDiagnostics[] | undefined,
+): MatlabTestDiagnostics[] {
if (!diagnostics) return [];
return Array.isArray(diagnostics) ? diagnostics : [diagnostics];
diff --git a/src/testResultsSummary.unit.test.ts b/src/testResultsSummary.unit.test.ts
index 700f57d..73fddf1 100644
--- a/src/testResultsSummary.unit.test.ts
+++ b/src/testResultsSummary.unit.test.ts
@@ -1,6 +1,6 @@
// Copyright 2025-2026 The MathWorks, Inc.
-import {jest, describe, it, expect, beforeAll} from "@jest/globals";
+import { jest, describe, it, expect, beforeAll } from "@jest/globals";
import * as path from "path";
import * as os from "os";
import * as nodeFs from "fs";
@@ -29,7 +29,7 @@ jest.unstable_mockModule("fs", () => ({
const core = await import("@actions/core");
const fs = await import("fs");
const testResultsSummary = await import("./testResultsSummary.js");
-const {MatlabTestStatus} = testResultsSummary;
+const { MatlabTestStatus } = testResultsSummary;
describe("Artifact Processing Tests", () => {
// Shared test data
@@ -59,11 +59,17 @@ describe("Artifact Processing Tests", () => {
return { osName: "windows", workspaceParent: "C:\\" };
if (platform.includes("linux") || platform.includes("unix") || platform.includes("aix"))
return { osName: "linux", workspaceParent: "/home/user/" };
- if (platform.includes("darwin")) return { osName: "mac", workspaceParent: "/Users/username/" };
+ if (platform.includes("darwin"))
+ return { osName: "mac", workspaceParent: "/Users/username/" };
throw new Error(`Unsupported OS: ${platform}`);
}
- function copyTestDataFile(osName: string, runnerTemp: string, runId: string, actionName: string) {
+ function copyTestDataFile(
+ osName: string,
+ runnerTemp: string,
+ runId: string,
+ actionName: string,
+ ) {
const sourceFilePath = path.join(
import.meta.dirname,
"test-data",
@@ -120,18 +126,10 @@ describe("Artifact Processing Tests", () => {
expect(testResults[0][0].TestCases[8].Name).toBe("testInvalidDateFormat");
expect(testResults[1][0].TestCases[0].Name).toBe("testNonLeapYear");
- expect(testResults[0][0].TestCases[0].Status).toBe(
- MatlabTestStatus.PASSED,
- );
- expect(testResults[0][0].TestCases[4].Status).toBe(
- MatlabTestStatus.FAILED,
- );
- expect(testResults[0][0].TestCases[8].Status).toBe(
- MatlabTestStatus.NOT_RUN,
- );
- expect(testResults[1][0].TestCases[0].Status).toBe(
- MatlabTestStatus.INCOMPLETE,
- );
+ expect(testResults[0][0].TestCases[0].Status).toBe(MatlabTestStatus.PASSED);
+ expect(testResults[0][0].TestCases[4].Status).toBe(MatlabTestStatus.FAILED);
+ expect(testResults[0][0].TestCases[8].Status).toBe(MatlabTestStatus.NOT_RUN);
+ expect(testResults[1][0].TestCases[0].Status).toBe(MatlabTestStatus.INCOMPLETE);
expect(testResults[0][0].TestCases[0].Duration).toBeCloseTo(0.1);
expect(testResults[0][0].TestCases[1].Duration).toBeCloseTo(0.11);
@@ -382,7 +380,11 @@ describe("Error Handling Tests", () => {
fs.writeFileSync(invalidJsonPath, "{ invalid json content");
try {
- const result = testResultsSummary.getTestResults(process.env.RUNNER_TEMP, process.env.GITHUB_RUN_ID, "");
+ const result = testResultsSummary.getTestResults(
+ process.env.RUNNER_TEMP,
+ process.env.GITHUB_RUN_ID,
+ "",
+ );
expect(result).toBeNull();
// Verify error was logged
@@ -419,9 +421,13 @@ describe("Error Handling Tests", () => {
fs.writeFileSync(validJsonPath, "[]"); // Empty array - valid JSON
try {
- const result = testResultsSummary.getTestResults(process.env.RUNNER_TEMP, process.env.GITHUB_RUN_ID, "");
+ const result = testResultsSummary.getTestResults(
+ process.env.RUNNER_TEMP,
+ process.env.GITHUB_RUN_ID,
+ "",
+ );
- if(result){
+ if (result) {
// Should still return results even if deletion fails
expect(result).toBeDefined();
expect(result.TestResults).toEqual([]);
@@ -450,4 +456,4 @@ describe("Error Handling Tests", () => {
}
}
});
-});
\ No newline at end of file
+});