├── .circleci └── config.yml ├── .github └── workflows │ └── ci.yml ├── .gitlab-ci.yml ├── Jenkinsfile ├── License.txt ├── README.md ├── SECURITY.md ├── azure-pipelines.yml ├── buildfile.m ├── code └── dayofyear.m └── tests ├── ParameterizedTestExample.m └── TestExamples.m /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | matlab: mathworks/matlab@1 4 | jobs: 5 | build: 6 | machine: 7 | image: ubuntu-2204:current 8 | steps: 9 | - checkout 10 | - matlab/install 11 | - matlab/run-tests: 12 | source-folder: code 13 | 14 | # As an alternative to run-tests, you can use run-command to execute a MATLAB script, function, or statement. 15 | # - matlab/run-command: 16 | # command: addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results); 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with MATLAB Actions 2 | 3 | name: MATLAB Build 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | 22 | # Steps represent a sequence of tasks that will be executed as part of the job 23 | steps: 24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 25 | - uses: actions/checkout@v4 26 | 27 | # Sets up MATLAB on a GitHub-hosted runner 28 | - name: Set up MATLAB 29 | uses: matlab-actions/setup-matlab@v2 30 | 31 | # Runs a set of commands using the runners shell 32 | - name: Run all tests 33 | uses: matlab-actions/run-tests@v2 34 | with: 35 | source-folder: code 36 | 37 | # You can use "run-build" to invoke the MATLAB build tool and run build tasks 38 | #- name: Run the default "test" task in the build file 39 | # uses: matlab-actions/run-build@v2 40 | 41 | # You can use "run-command" to execute custom MATLAB scripts, functions, or statements 42 | #- name: Run custom testing procedure 43 | # uses: matlab-actions/run-command@v2 44 | # with: 45 | # command: disp('Running my custom testing procedure!'); addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results); 46 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - test 3 | 4 | matlab-test-job: 5 | stage: test 6 | script: 7 | - matlab -batch "addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results);" 8 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Run MATLAB Tests') { 5 | steps { 6 | runMATLABTests( 7 | sourceFolder: ['code'] 8 | ) 9 | 10 | // As an alternative to runMATLABTests, you can use runMATLABCommand to execute a MATLAB script, function, or statement. 11 | // runMATLABCommand "addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results);" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, The MathWorks, Inc. 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 5 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 6 | 3. In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings. 7 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | | **Azure® DevOps** | **CircleCI®** | **GitHub® Actions** | 2 | |:---------------------------|:-----------------:|:----------------------------:| 3 | | [![Build Status](https://dev.azure.com/iat-ci/ci-configuration-examples/_apis/build/status/mathworks.ci-configuration-examples)](https://dev.azure.com/iat-ci/ci-configuration-examples/_build)
![Azure DevOps Coverage](https://img.shields.io/azure-devops/coverage/iat-ci/ci-configuration-examples/38) | [![CircleCI](https://circleci.com/gh/mathworks/ci-configuration-examples.svg?style=svg)](https://app.circleci.com/pipelines/github/mathworks/ci-configuration-examples)

| [![MATLAB](https://github.com/mathworks/ci-configuration-examples/actions/workflows/ci.yml/badge.svg)](https://github.com/mathworks/ci-configuration-examples/actions/workflows/ci.yml)

| 4 | 5 | 6 | # Continuous Integration (CI) configuration examples for MATLAB® 7 | 8 | Are you interested in automating your testing with CI? 9 | 10 | Not sure how to connect MATLAB with CI systems? 11 | 12 | We've got you covered! 13 | 14 | This repository makes it easy to run your MATLAB tests on some of the most common CI platforms. The configuration files take care of setting up MATLAB to work with the CI system and automatically executing your MATLAB tests. 15 | 16 | For advanced workflows, use the [`advanced-ci-configuration-examples`](https://github.com/mathworks/advanced-ci-configuration-examples) repository instead. 17 | 18 |
19 | 20 | 21 | ## Getting started 22 | ### Quick start guide 23 | Here's how to quickly get this repository running on a CI system: 24 | 1. Fork the repository to your own GitHub account 25 | 2. Go to one of the supported CI platforms and install the available MATLAB CI plugin ***Note:*** GitHub Actions does not require this step 26 | 3. Create a new CI job using your forked repository 27 | 4. Enjoy using CI with MATLAB! 28 | 29 | That's really it! 30 | 31 | To use your MATLAB code or Simulink® models with this repository, simply replace the existing code and tests in "code" and "tests" with your own code and tests. There's no need to modify any of the CI configuration files because they are all completely agnostic of the MATLAB code being used. 32 | 33 | ***Notes:*** 34 | * In most cases, the configuration files will automatically get picked up by the CI platform during step 3, but some platforms may require you to specify which file to use 35 | * The default branch for this repository is "main" 36 | 37 |
38 | 39 | 40 | ### Step-by-step getting started guide for GitHub Actions 41 | Our Continuous Integration with MATLAB and GitHub Actions Workshop provides a step-by-step guide for getting started with GitHub Actions. 42 | * https://github.com/mathworks/ci-with-matlab-and-github-actions-workshop 43 | 44 | 45 |
46 | 47 | 48 | ## Badges 49 | 50 | Badges look really great, but they're not always easy to set up. Take a look at the badges, badge code, and reference links below to get started with badges for your repository. 51 | 52 |
53 | 54 | | **Azure DevOps** | | 55 | |:---------------------------|:-| 56 | | Badges | [![Build Status](https://dev.azure.com/iat-ci/ci-configuration-examples/_apis/build/status/mathworks.ci-configuration-examples)](https://dev.azure.com/iat-ci/ci-configuration-examples/_build)
![Azure DevOps Coverage](https://img.shields.io/azure-devops/coverage/iat-ci/ci-configuration-examples/38) | 57 | | Badge Code | \[\!\[Build Status](https[]()://dev.azure.com/***AZURE_DEVOPS_ORG***/***AZURE_DEVOPS_PROJECT_NAME***/_apis/build/status/***GITHUB_USERNAME***.***GITHUB_REPO_NAME***)](https[]()://dev.azure.com/***AZURE_DEVOPS_ORG***/***AZURE_DEVOPS_PROJECT_NAME***/_build)

\!\[Azure DevOps Coverage](https[]()://img.shields.io/azure-devops/coverage/***AZURE_DEVOPS_ORG***/***AZURE_DEVOPS_PROJECT_NAME***/***AZURE_DEVOPS_DEFINITION_ID***) | 58 | | Badge Help | [Blog with helpful information for setting up Azure DevOps badges](https://gregorsuttie.com/2019/03/20/azure-devops-add-your-build-status-badges-to-your-wiki/) | 59 | 60 |
61 | 62 | | **CircleCI** | | 63 | |:------------------|:-| 64 | | Badge | [![CircleCI](https://circleci.com/gh/mathworks/ci-configuration-examples.svg?style=svg)](https://circleci.com/gh/mathworks/ci-configuration-examples) | 65 | | Badge Code | \[\!\[CircleCI](https[]()://circleci.com/***SOURCE_CONTROL_SYSTEM***/***GITHUB_USERNAME***/***GITHUB_REPO_NAME***.svg?style=svg)](https[]()://circleci.com/***SOURCE_CONTROL_SYSTEM***/***GITHUB_USERNAME***/***GITHUB_REPO_NAME***) | 66 | | Badge Help | [CircleCI documentation for setting up badges](https://circleci.com/docs/2.0/status-badges "CircleCI documentation for setting up badges") | 67 | 68 |
69 | 70 | | **GitHub Actions** | | 71 | |:-----------------------------|:-| 72 | | Badge | [![MATLAB](https://github.com/mathworks/ci-configuration-examples/actions/workflows/ci.yml/badge.svg)](https://github.com/mathworks/ci-configuration-examples/actions/workflows/ci.yml) | 73 | | Badge Code | \[\!\[MATLAB](https[]()://github.com/***GITHUB_USERNAME***/***GITHUB_REPO_NAME***/actions/workflows/ci.yml/badge.svg)](https[]()://github.com/***GITHUB_USERNAME***/***GITHUB_REPO_NAME***/actions/workflows/ci.yml) | 74 | | Badge Help | [GitHub Actions documentation for setting up badges](https://docs.github.com/en/actions/managing-workflow-runs/adding-a-workflow-status-badge) | 75 | 76 |
77 | 78 | | **GitLab CI/CD** | | 79 | |:--------------------------|:-| 80 | | Badge Code | \[\!\[Pipeline Status](https[]()://gitlab.com/***GITLAB_PROJECT_PATH***/badges/***DEFAULT_BRANCH_NAME***/pipeline.svg)](https[]()://gitlab.com/***GITLAB_PROJECT_PATH***) | 81 | | Badge Help | [GitLab CI/CD documentation for setting up badges](https://docs.gitlab.com/ee/user/project/badges.html "GitLab CI/CD documentation for setting up badges") | 82 | 83 |
84 | 85 | **How to use the Badge Code:** 86 | 1. Copy-paste the badge code into your README.md file 87 | * The badge code you copy should start with "`[!`" and not "`\[\!`" 88 | 2. Replace all ***BOLD+ITALIC*** names with your specific credentials/names 89 | * Replace ***GITHUB_ORG*** with your GitHub organization name (usually your GitHub username) 90 | * Replace ***GITHUB_REPO_NAME*** with the name of your GitHub repository 91 | * Replace ***SOURCE_CONTROL_SYSTEM*** with the name of the source control system you are pointing to (gh = GitHub, bb = BitBucket) 92 | * Replace ***AZURE_DEVOPS_ORG*** with the name of your Azure DevOps organization name (usually your Azure DevOps username) 93 | * Replace ***AZURE_DEVOPS_PROJECT_NAME*** with the name of the Azure DevOps project that will run the CI job 94 | * Replace ***AZURE_DEVOPS_DEFINITION_ID*** with the definition ID for your Azure DevOps pipeline 95 | * To find the definition ID for your Azure DevOps pipeline, you must: 96 | * go to your Azure DevOps project 97 | * select "Pipelines" from the left-side navigation menu 98 | * select the pipeline you want to get coverage for 99 | * look at the end of the resulting URL for the number in "definitionId=###" 100 | * Replace ***GITLAB_PROJECT_PATH*** with the path of your GitLab project 101 | * Replace ***DEFAULT_BRANCH_NAME*** with the repository branch name you want to get the pipeline status from 102 | 103 |
104 |
105 | 106 | 107 | ## Supported CI platforms 108 | * Azure DevOps 109 | * CircleCI 110 | * GitHub Actions 111 | * Jenkins™ 112 | * GitLab CI/CD 113 | 114 |
115 | 116 | 117 | ## About the code 118 | The primary goal of this repository is to provide a set of configuration files as templates that illustrate how to run MATLAB on various CI platforms (e.g., Azure DevOps, CircleCI, GitHub Actions, Jenkins). 119 | 120 | Each of these pipeline definitions does four things: 121 | 122 | 1. Install the latest MATLAB release on a Linux®-based build agent 123 | 2. Run all MATLAB tests in the root of your repository, including its subfolders 124 | 3. Produce a test results report (if necessary) 125 | 4. Produce a code coverage report in Cobertura XML format for the source folder 126 | * Currently, only Azure DevOps supports code coverage directly 127 | * To see an example of using [Codecov](https://about.codecov.io/) to show coverage results, please refer to [https://github.com/mathworks/matlab-codecov-example](https://github.com/mathworks/matlab-codecov-example) 128 | 129 | The example MATLAB code example `dayofyear.m` is a simple function takes a date string `"mm/dd/yyyy"` and returns the day-of-year number. 130 | 131 | Notes: 132 | * MATLAB already includes a day-of-year calculation using `day(d,"dayofyear")`, where `d` is a datetime object. This code is only used as an example since it is a concept that is familiar to most people. 133 | * The code coverage is intentionally set below 100% to show how missing coverage looks with badges. Uncomment the last test in `TestExamples.m` to increase the coverage to 100%. 134 | 135 | There are 2 test classes provided: 136 | 1. TestExamples.m - A simple set of equality and negative tests 137 | 2. ParameterizedTestExamples.m - A set of 12 equality tests set up using the parameterized test format 138 | 139 | The repository includes these files: 140 | 141 | | **File Path** | **Description** | 142 | |:---------------------------|:----------------| 143 | | [`code/dayofyear.m`](code/dayofyear.m) | The [`dayofyear`](code/dayofyear.m) function returns the day-of-year number for a given date string "mm/dd/yyyy" | 144 | | [`tests/TestExamples.m`](tests/TestExamples.m) | The [`TestExamples`](tests/TestExamples.m) class provides a few equality and negative tests for the [`dayofyear`](code/dayofyear.m) function | 145 | | [`tests/ParameterizedTestExample.m`](tests/ParameterizedTestExample.m) | The [`ParameterizedTestExample`](tests/ParameterizedTestExample.m) class provides 12 tests for the [`dayofyear`](code/dayofyear.m) function using the parameterized test format | 146 | | [`azure-pipelines.yml`](###Azure-DevOps) | The [`azure-pipelines.yml`](azure-pipelines.yml) file defines the pipeline that runs on [Azure DevOps](https://marketplace.visualstudio.com/items?itemName=MathWorks.matlab-azure-devops-extension). | 147 | | [`.circleci/config.yml`](###CircleCI) | The [`config.yml`](.circleci/config.yml) file defines the pipeline that runs on [CircleCI](https://circleci.com/orbs/registry/orb/mathworks/matlab) | 148 | | [`.github/workflows/ci.yml`](###GitHub-Actions) | The [`ci.yml`](.github/workflows/ci.yml) file defines the pipeline that runs on [GitHub Actions](https://github.com/matlab-actions/overview) | 149 | | [`Jenkinsfile`](###Jenkins) | The [`Jenkinsfile`](Jenkinsfile) file defines the pipeline that runs on [Jenkins](https://plugins.jenkins.io/matlab/) | 150 | | [`.gitlab-ci.yml`](###GitLab-CI/CD) | The [`.gitlab-ci.yml`](.gitlab-ci.yml) file defines the pipeline that runs on [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) | 151 | 152 |
153 | 154 | 155 | ## CI configuration files 156 | 157 | ### Azure DevOps 158 | ```yml 159 | pool: 160 | vmImage: ubuntu-latest 161 | steps: 162 | - task: InstallMATLAB@1 163 | - task: RunMATLABTests@1 164 | inputs: 165 | sourceFolder: code 166 | codeCoverageCobertura: code-coverage/coverage.xml 167 | testResultsJUnit: test-results/results.xml 168 | - task: PublishTestResults@2 169 | inputs: 170 | testResultsFormat: 'JUnit' 171 | testResultsFiles: 'test-results/results.xml' 172 | - task: PublishCodeCoverageResults@1 173 | inputs: 174 | codeCoverageTool: 'Cobertura' 175 | summaryFileLocation: 'code-coverage/coverage.xml' 176 | pathToSources: 'code/' 177 | 178 | # As an alternative to RunMATLABTests, you can use RunMATLABCommand to execute a MATLAB script, function, or statement. 179 | # - task: RunMATLABCommand@1 180 | # inputs: 181 | # command: addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results); 182 | ``` 183 | 184 |
185 | 186 | ### CircleCI 187 | ```yml 188 | version: 2.1 189 | orbs: 190 | matlab: mathworks/matlab@1 191 | jobs: 192 | build: 193 | machine: 194 | image: ubuntu-2204:current 195 | steps: 196 | - checkout 197 | - matlab/install 198 | - matlab/run-tests: 199 | source-folder: code 200 | 201 | # As an alternative to run-tests, you can use run-command to execute a MATLAB script, function, or statement. 202 | # - matlab/run-command: 203 | # command: addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results); 204 | ``` 205 | 206 |
207 | 208 | ### GitHub Actions 209 | ```yml 210 | # This is a basic workflow to help you get started with MATLAB Actions 211 | 212 | name: MATLAB Build 213 | 214 | # Controls when the action will run. 215 | on: 216 | # Triggers the workflow on push or pull request events but only for the main branch 217 | push: 218 | branches: [ main ] 219 | pull_request: 220 | branches: [ main ] 221 | 222 | # Allows you to run this workflow manually from the Actions tab 223 | workflow_dispatch: 224 | 225 | jobs: 226 | # This workflow contains a single job called "build" 227 | build: 228 | # The type of runner that the job will run on 229 | runs-on: ubuntu-latest 230 | 231 | # Steps represent a sequence of tasks that will be executed as part of the job 232 | steps: 233 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 234 | - uses: actions/checkout@v4 235 | 236 | # Sets up MATLAB on a GitHub-hosted runner 237 | - name: Set up MATLAB 238 | uses: matlab-actions/setup-matlab@v2 239 | 240 | # Runs a set of commands using the runners shell 241 | - name: Run all tests 242 | uses: matlab-actions/run-tests@v2 243 | with: 244 | source-folder: code 245 | 246 | # You can use "run-build" to invoke the MATLAB build tool and run build tasks 247 | #- name: Run the default "test" task in the build file 248 | # uses: matlab-actions/run-build@v2 249 | 250 | # You can use "run-command" to execute custom MATLAB scripts, functions, or statements 251 | #- name: Run custom testing procedure 252 | # uses: matlab-actions/run-command@v2 253 | # with: 254 | # command: disp('Running my custom testing procedure!'); addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results); 255 | ``` 256 | 257 |
258 | 259 | ### Jenkins 260 | ```groovy 261 | pipeline { 262 | agent any 263 | stages { 264 | stage('Run MATLAB Tests') { 265 | steps { 266 | runMATLABTests( 267 | sourceFolder: 'code' 268 | ) 269 | 270 | // As an alternative to runMATLABTests, you can use runMATLABCommand to execute a MATLAB script, function, or statement. 271 | // runMATLABCommand "addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results);" 272 | } 273 | } 274 | } 275 | } 276 | ``` 277 | 278 |
279 | 280 | ### GitLab CI/CD 281 | ```yml 282 | stages: 283 | - matlab-test 284 | 285 | matlab-test: 286 | stage: matlab-test 287 | script: 288 | - matlab -batch "addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results);" 289 | ``` 290 |
291 | 292 | 293 | ## Caveats 294 | * On cloud-hosted agents provided by Azure DevOps, CircleCI, and GitHub Actions, you need a [MATLAB batch licensing token](https://github.com/mathworks-ref-arch/matlab-dockerfile/blob/main/alternates/non-interactive/MATLAB-BATCH.md#matlab-batch-licensing-token) if your project is private or if your pipeline includes transformation products, such as MATLAB Coder™ and MATLAB Compiler™. You can request a token by contacting MathWorks® at [batch-tokens@mathworks.com](mailto:batch-tokens@mathworks.com). 295 | 296 |
297 | 298 | 299 | ## See Also 300 | - [Advanced Continuous Integration (CI) configuration examples for MATLAB](https://github.com/mathworks/advanced-ci-configuration-examples) 301 | - [Continuous Integration with MATLAB and Simulink](https://www.mathworks.com/solutions/continuous-integration.html) 302 | 303 |
304 | 305 | 306 | ## Contact Us 307 | If you have any questions or suggestions, please contact MathWorks at [continuous-integration@mathworks.com](mailto:continuous-integration@mathworks.com). 308 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Vulnerabilities 2 | 3 | If you believe you have discovered a security vulnerability, please report it to 4 | [security@mathworks.com](mailto:security@mathworks.com). Please see 5 | [MathWorks Vulnerability Disclosure Policy for Security Researchers](https://www.mathworks.com/company/aboutus/policies_statements/vulnerability-disclosure-policy.html) 6 | for additional information. -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | vmImage: ubuntu-latest 3 | steps: 4 | - task: InstallMATLAB@1 5 | - task: RunMATLABTests@1 6 | inputs: 7 | sourceFolder: code 8 | codeCoverageCobertura: code-coverage/coverage.xml 9 | testResultsJUnit: test-results/results.xml 10 | - task: PublishTestResults@2 11 | condition: always() 12 | inputs: 13 | testResultsFormat: 'JUnit' 14 | testResultsFiles: 'test-results/results.xml' 15 | - task: PublishCodeCoverageResults@1 16 | condition: always() 17 | inputs: 18 | codeCoverageTool: 'Cobertura' 19 | summaryFileLocation: 'code-coverage/coverage.xml' 20 | pathToSources: 'code/' 21 | 22 | # As an alternative to RunMATLABTests, you can use RunMATLABCommand to execute a MATLAB script, function, or statement. 23 | # - task: RunMATLABCommand@1 24 | # inputs: 25 | # command: addpath('code'); results = runtests('IncludeSubfolders', true); assertSuccess(results); 26 | -------------------------------------------------------------------------------- /buildfile.m: -------------------------------------------------------------------------------- 1 | function plan = buildfile 2 | 3 | % Add the source folder to the path 4 | addpath("code"); 5 | 6 | % Create a plan 7 | plan = buildplan(localfunctions); 8 | 9 | % Add a task to run tests 10 | plan("test") = matlab.buildtool.tasks.TestTask("tests"); 11 | 12 | % Make the "test" task the default task in the plan 13 | plan.DefaultTasks = "test"; 14 | 15 | end 16 | 17 | -------------------------------------------------------------------------------- /code/dayofyear.m: -------------------------------------------------------------------------------- 1 | function doy = dayofyear(mmddyy,dateFormat) 2 | %DAYOFYEAR Converts a date string ("mm/dd/yyyy") to the day number of the 3 | %year. 4 | 5 | % NOTE: MATLAB already does easily this using: 6 | % doy = day(d,"dayofyear") 7 | % where d is a datetime object 8 | 9 | % Copyright 2022 The MathWorks, Inc. 10 | 11 | arguments 12 | mmddyy string; 13 | dateFormat (1,1) string {mustBeMember(dateFormat,["mm/dd/yyyy","dd/mm/yyyy"])} = "mm/dd/yyyy"; 14 | end 15 | 16 | % Check that mmddyy was provided in the appropriate format 17 | if numel(split(mmddyy,"/")) ~= 3 18 | error("dayofyear:InvalidDateFormat","Invalid date string. Expected date formatted as dd/mm/yyyy.") 19 | end 20 | 21 | % Create a datetime object depending on the dateFormat provided 22 | if dateFormat == "mm/dd/yyyy" 23 | d = datetime(mmddyy,"Format","MM/dd/uuuu"); 24 | else 25 | d = datetime(mmddyy,"Format","dd/MM/uuuu"); 26 | end 27 | 28 | % Initialize the days per month 29 | daysPerMonth = [ ... 30 | 31; % January 31 | 28; % February 32 | 31; % March 33 | 30; % April 34 | 31; % May 35 | 30; % June 36 | 31; % July 37 | 31; % August 38 | 30; % September 39 | 31; % October 40 | 30; % November 41 | 31]; % December 42 | 43 | % Check for leap year 44 | if mod(d.Year,4) == 0 45 | % This is a leap year, so change February to 29 days 46 | daysPerMonth(2) = 29; 47 | end 48 | 49 | % Calculate day of year 50 | doy = sum(daysPerMonth(1:d.Month-1)) + d.Day; 51 | 52 | 53 | end 54 | 55 | -------------------------------------------------------------------------------- /tests/ParameterizedTestExample.m: -------------------------------------------------------------------------------- 1 | classdef ParameterizedTestExample < matlab.unittest.TestCase 2 | % Creates 12 test points, one test point for the 15th day of every month of 2021 3 | 4 | % Copyright 2022 The MathWorks, Inc. 5 | 6 | properties (TestParameter) 7 | monthNum = num2cell(1:12); 8 | dayNum = {15}; 9 | yearNum = {2021}; 10 | end 11 | 12 | methods (Test) 13 | function testDayofyear(testCase,monthNum,dayNum,yearNum) 14 | % Convert numeric values to mm/dd/yyyy string 15 | % Note: MATLAB will automatically convert numbers to strings 16 | % when performing number+string arithmetic 17 | dateStr = monthNum + "/" + dayNum + "/" + yearNum; 18 | 19 | % Compute expected result 20 | dt = datetime(dateStr,"Format","MM/dd/uuuu"); 21 | doyExpected = day(dt,"dayofyear"); 22 | 23 | % Compute actual result 24 | doyActual = dayofyear(dateStr); 25 | 26 | % Verify that the actual result matches the expected result 27 | testCase.verifyEqual(doyActual,doyExpected) 28 | end 29 | end 30 | 31 | end 32 | 33 | -------------------------------------------------------------------------------- /tests/TestExamples.m: -------------------------------------------------------------------------------- 1 | classdef TestExamples < matlab.unittest.TestCase 2 | % TestExamples contains a set of 4 simple tests: 3 | % 1) an equality test for a non-leap year date 4 | % 2) an equality test for a leap year date 5 | % 3) a negative test for an invalid date format input 6 | % 4) a negative test for a correct date format but an invalid date 7 | % 5) an equality test for a non-leap year date using the alternate 8 | % dateFormat (COMMENTED OUT) 9 | % 10 | % Notes: 11 | % A) A negative test verifies that the code errors/fails in an 12 | % expected way (e.g., the code gives the right error for a 13 | % specific bad input) 14 | % B) The 5th test is included for completeness, but is commented 15 | % out to illustrate missing code coverage in continous 16 | % integration (CI) systems 17 | 18 | % Copyright 2022 The MathWorks, Inc. 19 | 20 | methods (Test) 21 | 22 | function testNonLeapYear(testCase) 23 | % Create non-leap year date of March 1st, 2021 24 | dateStr = "03/01/2021"; 25 | 26 | % Calculate expected result 27 | dt = datetime(dateStr,"Format","MM/dd/uuuu"); 28 | doyExpected = day(dt,"dayofyear"); 29 | 30 | % Get actual result 31 | doyActual = dayofyear(dateStr); 32 | 33 | % Verify that the two are equal 34 | testCase.verifyEqual(doyActual,doyExpected) 35 | end 36 | 37 | function testLeapYear(testCase) 38 | % Create leap year date of March 1st, 2020 39 | dateStr = "03/01/2020"; 40 | 41 | % Calculate expected result 42 | dt = datetime(dateStr,"Format","MM/dd/uuuu"); 43 | doyExpected = day(dt,"dayofyear"); 44 | 45 | % Get actual result 46 | doyActual = dayofyear(dateStr); 47 | 48 | % Verify that the two are equal 49 | testCase.verifyEqual(doyActual,doyExpected) 50 | end 51 | 52 | function testInvalidDateFormat(testCase) 53 | % Create invalid date of April 1st, 2021 54 | dateStr = "04-01-2021"; 55 | 56 | % Verify that our function throws an error 57 | testCase.verifyError(@() dayofyear(dateStr),"dayofyear:InvalidDateFormat"); 58 | end 59 | 60 | function testCorrectDateFormatButInvalidDate(testCase) 61 | % Create invalid date of February 30th, 2021 62 | dateStr = "02/30/2021"; 63 | 64 | % Verify that our function throws an error 65 | testCase.verifyError(@() dayofyear(dateStr),"MATLAB:datetime:ParseErr"); 66 | end 67 | 68 | % function testAlternateDateFormat(testCase) 69 | % % Create date of April 1st, 2021 in alternate date format 70 | % dateStr = "01/04/2021"; 71 | % dateFormat = "dd/mm/yyyy"; 72 | % 73 | % % Calculate expected result 74 | % dt = datetime(dateStr,"Format","dd/MM/uuuu"); 75 | % doyExpected = day(dt,"dayofyear"); 76 | % 77 | % % Get actual result 78 | % doyActual = dayofyear(dateStr,dateFormat); 79 | % 80 | % % Verify that the two are equal 81 | % testCase.verifyEqual(doyActual,doyExpected) 82 | % end 83 | 84 | end 85 | 86 | end 87 | 88 | --------------------------------------------------------------------------------