From 21b63afa2ca2754c6bdb1c74e1bba181c4573811 Mon Sep 17 00:00:00 2001 From: mw-hrastega <48831250+mw-hrastega@users.noreply.github.com> Date: Fri, 6 Oct 2023 18:20:28 -0400 Subject: [PATCH 01/20] documenting MATLAB startup options (#253) * addressing feedback --- CONFIGDOC.md | 188 +++++++++++++++++------------------ examples/Run-MATLAB-Tests.md | 34 +++---- 2 files changed, 110 insertions(+), 112 deletions(-) diff --git a/CONFIGDOC.md b/CONFIGDOC.md index af9569db..7feb261f 100644 --- a/CONFIGDOC.md +++ b/CONFIGDOC.md @@ -1,8 +1,8 @@ # Plugin Configuration Guide -When you define an automated pipeline of tasks in Jenkins™, whether in the web UI or with a [`Jenkinsfile`](https://www.jenkins.io/doc/book/pipeline/jenkinsfile/), you can use the plugin to run your MATLAB® code or Simulink® models. This guide demonstrates how to configure the plugin and use it in freestyle, multi-configuration, and pipeline projects. +When you define an automated pipeline of tasks in Jenkins™, whether in the web UI or with a [`Jenkinsfile`](https://www.jenkins.io/doc/book/pipeline/jenkinsfile/), you can use this plugin to run your MATLAB® code or Simulink® models. This guide demonstrates how to configure the plugin and use it in freestyle, multi-configuration, and pipeline projects. -> :information_source: **Note:** To run MATLAB code or Simulink models, Jenkins requires a valid MATLAB or Simulink license. If you have installed Jenkins as a Windows® service application, you need to provide a MathWorks® username and password in the **Jenkins Properties** window (accessible from within the Windows Task Manager). +> :information_source: **Note:** To run MATLAB code or Simulink models, Jenkins requires a valid MATLAB or Simulink license. If you have Jenkins installed as a Windows® service application, you need to provide a MathWorks® username and password in the **Jenkins Properties** window (accessible from within the Windows Task Manager). - [Configure Plugin in Web UI](#configure-plugin-in-web-ui) - [Use MATLAB in Build](#use-matlab-in-build) @@ -16,9 +16,9 @@ When you define an automated pipeline of tasks in Jenkins™, whether in the - [Add User-Defined Axis](#add-user-defined-axis) - [Set Up Pipeline Project](#set-up-pipeline-project) - [Add MATLAB to System Path](#add-matlab-to-system-path) - - [Use the runMATLABBuild Step](#use-the-runmatlabbuild-step) - - [Use the runMATLABTests Step](#use-the-runmatlabtests-step) - - [Use the runMATLABCommand Step](#use-the-runmatlabcommand-step) + - [Use the `runMATLABBuild` Step](#use-the-runmatlabbuild-step) + - [Use the `runMATLABTests` Step](#use-the-runmatlabtests-step) + - [Use the `runMATLABCommand` Step](#use-the-runmatlabcommand-step) - [Use MATLAB in Matrix Build](#use-matlab-in-matrix-build) - [Register MATLAB as Jenkins Tool](#register-matlab-as-jenkins-tool) - [Use MATLAB as a Tool in Pipeline Project](#use-matlab-as-a-tool-in-pipeline-project) @@ -27,11 +27,11 @@ When you define an automated pipeline of tasks in Jenkins™, whether in the You can use the web UI provided by Jenkins to configure the plugin in freestyle and multi-configuration projects. To run MATLAB or Simulink in a pipeline project, see [Set Up Pipeline Project](#set-up-pipeline-project). ### Use MATLAB in Build -Once you install the plugin, **Use MATLAB version** appears in the **Build Environment** section of the project configuration window. Select **Use MATLAB version** to specify the MATLAB version you want to use in the build. You can select one of the MATLAB versions that have been registered as Jenkins tools, or you can select **Custom** if you want to specify a different version. For more information about registering a MATLAB version as a tool, see [Register MATLAB as Jenkins Tool](#register-matlab-as-jenkins-tool). +Once you install the plugin, the **Use MATLAB version** option appears in the **Build Environment** section of the project configuration window. Select **Use MATLAB version** to specify the MATLAB version you want to use in the build. You can select one of the MATLAB versions that have been registered as Jenkins tools, or you can select `Custom` if you want to specify a different version. For more information about registering a MATLAB version as a tool, see [Register MATLAB as Jenkins Tool](#register-matlab-as-jenkins-tool). -In this example, the list includes two registered tools as well as the option for specifying a custom installation. If you select **Custom**, a **MATLAB root** box appears in the UI. You must enter the full path to your preferred MATLAB root folder in this box. +In this example, the list includes two registered tools as well as the option for specifying a custom installation. If you select `Custom`, a **MATLAB root** box appears in the UI. You must enter the full path to your preferred MATLAB root folder in this box. -![use_matlab_version_tool](https://user-images.githubusercontent.com/48831250/217575011-ab82e8bf-c673-4364-946a-cdd54eb75c39.png) +![use_matlab_version_tool](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/1ef811c5-c69a-41c3-8649-55c5f2239776) When you specify a MATLAB version in the **Build Environment** section, the plugin prepends its `bin` folder to the system PATH environment variable of the build agent and invokes it to perform the build. If the build agent already has your preferred MATLAB version on the path, then you are not required to select **Use MATLAB version**. In this case, the plugin uses the topmost MATLAB version on the system path. The build fails if the operating system cannot find MATLAB on the path. @@ -39,9 +39,9 @@ You can use the [`matlabroot`](https://www.mathworks.com/help/matlab/ref/matlabr | Platform | Path to MATLAB Root Folder | |--------------|---------------------------------| -| Windows | C:\Program Files\MATLAB\R2022b | -| Linux® | /usr/local/MATLAB/R2022b | -| macOS | /Applications/MATLAB_R2022b.app | +| Windows | C:\Program Files\MATLAB\R2023b | +| Linux® | /usr/local/MATLAB/R2023b | +| macOS | /Applications/MATLAB_R2023b.app | ### Specify Build Steps When you set up the **Build Steps** section of the project configuration window, the plugin provides you with three build steps: @@ -50,6 +50,10 @@ When you set up the **Build Steps** section of the project configuration window, * To run MATLAB and Simulink tests and generate artifacts, use the [Run MATLAB Tests](#run-matlab-tests) step. * To run a MATLAB script, function, or statement, use the [Run MATLAB Command](#run-matlab-command) step. +You can specify optional MATLAB startup options for a step by first selecting **Startup options** and then populating the box that appears in the step configuration interface. For example, specify `-nojvm` to start MATLAB without the JVM™ software. If you specify more than one startup option, use a space to separate them (for example, `-nojvm -logfile "output.log"`). For more information about MATLAB startup options, see [Commonly Used Startup Options](https://www.mathworks.com/help/matlab/matlab_env/commonly-used-startup-options.html). + +> :information_source: **Note:** Selecting **Startup options** to specify the `-batch` or `-r` option is not supported. + If you use a source code management (SCM) system such as Git™, then your project should include the appropriate SCM configuration to check out the code before it can invoke the plugin. If you do not use any SCM systems to manage your code, then an additional build step might be required to ensure that the code is available in the project workspace before the build starts. #### Run MATLAB Build @@ -57,14 +61,14 @@ The **Run MATLAB Build** step lets you run a build using the [MATLAB build tool] Specify the tasks you want to execute in the **Tasks** box. If you specify more than one task, use a space to separate them. If you do not specify any tasks, the plugin runs the default tasks in `buildfile.m` as well as all the tasks on which they depend. For example, enter `mytask` in the **Tasks** box to run a task named `mytask` as well as all the tasks on which it depends. -![run_matlab_build](https://user-images.githubusercontent.com/48831250/217647265-38b0530f-f9f4-43a7-91ef-719e8d0a545e.png) +![run_matlab_build](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/b0df3645-e8df-48fe-8dd3-e429706dd61c) -MATLAB exits with exit code 0 if the specified tasks run successfully. Otherwise, MATLAB terminates with a nonzero exit code, which causes the Jenkins build to fail. +MATLAB exits with exit code 0 if the specified tasks run without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the Jenkins build to fail. When you use this step, a file named `buildfile.m` must be in the root of your repository. For more information about the build tool, see [Create and Run Tasks Using Build Tool](https://www.mathworks.com/help/matlab/matlab_prog/create-and-run-tasks-using-build-tool.html). #### Run MATLAB Tests -The **Run MATLAB Tests** build step lets you run MATLAB and Simulink tests and generate artifacts such as test results in JUnit XML format and code coverage results in Cobertura XML format. By default, the plugin includes any test files in your [MATLAB project](https://www.mathworks.com/help/matlab/projects.html) that have a `Test` label. If your build does not use a MATLAB project, or if it uses a MATLAB release before R2019a, then the plugin includes all tests in the root of your repository and in any of its subfolders. +The **Run MATLAB Tests** build step lets you run MATLAB and Simulink tests and generate artifacts, such as test results in JUnit-style XML format and code coverage results in Cobertura XML format. By default, the plugin includes any test files in your [MATLAB project](https://www.mathworks.com/help/matlab/projects.html) that have a `Test` label. If your build does not use a MATLAB project, or if it uses a MATLAB release before R2019a, then the plugin includes all tests in the root of your repository and in any of its subfolders. You can customize the **Run MATLAB Tests** build step in the step configuration interface. For example, you can add source folders to the MATLAB search path, control which tests to run, and generate various test and coverage artifacts. If you do not select any of the existing options, all the tests in your project run, and any test failure causes the build to fail. @@ -94,7 +98,7 @@ To generate test and coverage artifacts, select options in the **Generate Test A The **Run MATLAB Tests** build step produces a MATLAB script file and uses it to run the tests and generate the artifacts. The plugin writes the contents of this file to the build log. You can review the build log in **Console Output** to understand the testing workflow. -Artifacts to generate with the plugin are subject to these restrictions: +Artifacts that the plugin generates are subject to these restrictions: * Producing a PDF test report on macOS platforms is supported in MATLAB R2020b and later. * Exporting Simulink Test™ Manager results requires a Simulink Test license and is supported in MATLAB R2019a and later. * Collecting model coverage results requires a Simulink Coverage™ license and is supported in MATLAB R2018b and later. @@ -106,11 +110,11 @@ Specify the MATLAB script, function, or statement you want to execute in the **C For example, enter `myscript` in the **Command** box to run a script named `myscript.m` in the root of your repository. -![run_matlab_command](https://user-images.githubusercontent.com/48831250/217652304-8f7b351a-c52f-4f2b-a911-f2bc4c94dec0.png) +![run_matlab_command](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/45a99722-d872-403b-8c1a-90e23199ba47) -MATLAB exits with exit code 0 if the specified script, function, or statement executes successfully without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the build to fail. To fail the build in certain conditions, use the [`assert`](https://www.mathworks.com/help/matlab/ref/assert.html) or [`error`](https://www.mathworks.com/help/matlab/ref/error.html) functions. +MATLAB exits with exit code 0 if the specified script, function, or statement executes without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the Jenkins build to fail. To fail the build in certain conditions, use the [`assert`](https://www.mathworks.com/help/matlab/ref/assert.html) or [`error`](https://www.mathworks.com/help/matlab/ref/error.html) function. -When you use this step, all the required files must be on the MATLAB search path. If your script or function is not in the root of your repository, you can use the [`addpath`](https://www.mathworks.com/help/matlab/ref/addpath.html), [`cd`](https://www.mathworks.com/help/matlab/ref/cd.html), or [`run`](https://www.mathworks.com/help/matlab/ref/run.html) functions to ensure that it is on the path when invoked. For example, to run `myscript.m` in a folder named `myfolder` located in the root of the repository, you can specify the contents of the **Command** box like this: +When you use this step, all the required files must be on the MATLAB search path. If your script or function is not in the root of your repository, you can use the [`addpath`](https://www.mathworks.com/help/matlab/ref/addpath.html), [`cd`](https://www.mathworks.com/help/matlab/ref/cd.html), or [`run`](https://www.mathworks.com/help/matlab/ref/run.html) function to ensure that it is on the path when invoked. For example, to run `myscript.m` in a folder named `myfolder` located in the root of the repository, you can specify the contents of the **Command** box like this: `addpath("myfolder"), myscript` @@ -119,7 +123,7 @@ To configure the plugin for a freestyle project, specify the MATLAB version to u To specify the MATLAB version, select **Use MATLAB version** in the **Build Environment** section of the project configuration window. Then, specify the MATLAB version that Jenkins should use in the build. You can skip this step if MATLAB has already been added to the path on the build agent. -![build_environment](https://user-images.githubusercontent.com/48831250/217673448-961f14de-6985-453e-944f-96b9ffe29b99.png) +![build_environment](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/6fa3187a-5674-4435-9c69-4210a21b8d88) To run MATLAB code and Simulink models, specify the appropriate build steps in the **Build Steps** section: * If you add the [**Run MATLAB Build**](#run-matlab-build) step, specify your MATLAB build tasks in the **Tasks** box. @@ -136,12 +140,12 @@ To configure the plugin for a multi-configuration project, specify the MATLAB ve There are two ways to specify multiple MATLAB versions in a multi-configuration project: using the **MATLAB** axis or using a user-defined axis. -![add_axis](https://user-images.githubusercontent.com/48831250/217654844-0b35bf40-063d-4f1e-a45a-d2fd23f19c07.png) +![add_axis](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/8d134ca1-892e-4014-98e3-14fd8fbb3024) ### Add MATLAB Axis If your Jenkins instance includes MATLAB versions registered as tools, then **MATLAB** appears as an option when you click **Add axis** in the **Configuration Matrix** section. By adding the **MATLAB** axis, you can select MATLAB versions and add them as axis values to your matrix configuration. The list includes all MATLAB versions that have been registered as Jenkins tools. In this example, there are two MATLAB versions registered as tools. In each build iteration, the plugin prepends one of the selected versions to the PATH environment variable and invokes it to run the build. -![matlab_axis](https://user-images.githubusercontent.com/48831250/217655206-0fdade64-17f5-4604-a730-50b90871e939.png) +![matlab_axis](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/047283bb-782c-4437-af3b-ce296e73cf1a) For more information about registering a MATLAB version as a tool, see [Register MATLAB as Jenkins Tool](#register-matlab-as-jenkins-tool). @@ -150,9 +154,9 @@ For more information about registering a MATLAB version as a tool, see [Register ### Add User-Defined Axis If you do not specify the **MATLAB** axis, add a user-defined axis in the **Configuration Matrix** section to specify the MATLAB versions in the build. Enter the name of the axis in the **Name** box and its values in the **Values** box. Separate the values with a space. For instance, specify two MATLAB versions to run the same set of tests. -![user_defined_axis](https://user-images.githubusercontent.com/48831250/217655769-5e1c19be-372f-460c-9c11-456dc749e142.png) +![user_defined_axis](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/ee8cbdd6-f278-43ca-9580-99fb6d25853e) -When you add a user-defined axis to specify MATLAB versions, you must also specify where they are installed. To do this, select **Use MATLAB version** in the **Build Environment** section and then construct a root folder path using the axis name. In this example, `$VERSION` in the **MATLAB root** box is replaced by one axis value per build iteration. +When you add a user-defined axis to specify MATLAB versions, you must also specify where they are installed. To specify installation locations, select **Use MATLAB version** in the **Build Environment** section and then construct a root folder path using the axis name. In this example, `$VERSION` in the **MATLAB root** box is replaced by one axis value per build iteration. ![build_environment_matrix](https://user-images.githubusercontent.com/48831250/217656233-4b48181f-4236-4bb4-9a28-20cc119fb859.png) @@ -162,16 +166,13 @@ A multi-configuration project creates a separate workspace for each user-defined You can add several axes in the **Configuration Matrix** section. For example, add the **MATLAB** axis to specify MATLAB versions and the user-defined `TEST_TAG` axis to specify the test tags for a group of tests. -![axis_matlab_testtag](https://user-images.githubusercontent.com/48831250/217656693-e01f8c6c-dd10-481e-9d1d-f32c48ac9365.png) +![axis_matlab_testtag](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/2dca099a-d316-4f90-8b4b-b09ac5c83819) Once you have specified the axes, add the required build steps in the **Build Steps** section: * If you add the [**Run MATLAB Build**](#run-matlab-build) step, specify your MATLAB build tasks in the **Tasks** box. - * If you add the [**Run MATLAB Tests**](#run-matlab-tests) step, specify your source code, test suite filters, run customization options, and test and coverage artifacts to be generated in the project workspace. - * If you add the [**Run MATLAB Command**](#run-matlab-command) step, specify your MATLAB script, function, or statement in the **Command** box. You can use the user-defined axes to specify the contents of the **Command** box. For example: - ``` results = runtests(pwd,"Tag","$TEST_TAG"); assertSuccess(results); ``` @@ -185,30 +186,30 @@ When you define your pipeline with a `Jenkinsfile`, the plugin provides you with To configure the plugin for a pipeline project: 1) Define your pipeline in a `Jenkinsfile` in the root of your repository. -2) In the **Pipeline** section of the project configuration window, select **Pipeline script from SCM** from the **Definition** list. +2) In the **Pipeline** section of the project configuration window, select `Pipeline script from SCM` from the **Definition** list. 3) Select your source control system from the **SCM** list. 4) Paste your repository URL into the **Repository URL** box. -You also can define your pipeline directly in the project configuration window. If you select **Pipeline script** from the **Definition** list, you can author your pipeline code in the **Script** box. When you define your pipeline this way, it must include an additional stage to check out your code from source control. +You can also define your pipeline directly in the project configuration window. If you select `Pipeline script` from the **Definition** list, you can author your pipeline code in the **Script** box. When you define your pipeline this way, it must include an additional stage to check out your code from source control. ### Add MATLAB to System Path -When the plugin executes MATLAB related steps in your pipeline, it uses the topmost MATLAB version on the system path. If the PATH environment variable of the build agent does not include any MATLAB versions, you must update the variable with the MATLAB root folder that should be used for the build. +When the plugin executes steps that use MATLAB in your pipeline, the plugin uses the topmost MATLAB version on the system path. If the PATH environment variable of the build agent does not include any MATLAB versions, you must update the variable with the MATLAB root folder that should be used for the build. -To update the system PATH environment variable using declarative pipeline syntax, use an `environment` block in your `Jenkinsfile`. For example, prepend MATLAB R2022b to the system PATH environment variable and use it to run your command. +To update the system PATH environment variable using declarative pipeline syntax, use an `environment` block in your `Jenkinsfile`. For example, prepend MATLAB R2023b to the system PATH environment variable and use it to run your command. ```groovy // Declarative Pipeline pipeline { agent any environment { - PATH = "C:\\Program Files\\MATLAB\\R2022b\\bin;${PATH}" // Windows agent - // PATH = "/usr/local/MATLAB/R2022b/bin:${PATH}" // Linux agent - // PATH = "/Applications/MATLAB_R2022b.app/bin:${PATH}" // macOS agent + PATH = "C:\\Program Files\\MATLAB\\R2023b\\bin;${PATH}" // Windows agent + // PATH = "/usr/local/MATLAB/R2023b/bin:${PATH}" // Linux agent + // PATH = "/Applications/MATLAB_R2023b.app/bin:${PATH}" // macOS agent } stages { stage('Run MATLAB Command') { steps { - runMATLABCommand "disp('Hello World!')" + runMATLABCommand(command: 'disp("Hello World!")') } } } @@ -220,21 +221,21 @@ If you define your pipeline using scripted pipeline syntax, set the PATH environ ```groovy // Scripted Pipeline node { - env.PATH = "C:\\Program Files\\MATLAB\\R2022b\\bin;${env.PATH}" //Windows agent - // env.PATH = "/usr/local/MATLAB/R2022b/bin:${env.PATH}" //Linux agent - // env.PATH = "/Applications/MATLAB_R2022b.app/bin:${env.PATH}" //macOS agent - runMATLABCommand "disp('Hello World!')" + env.PATH = "C:\\Program Files\\MATLAB\\R2023b\\bin;${env.PATH}" // Windows agent + // env.PATH = "/usr/local/MATLAB/R2023b/bin:${env.PATH}" // Linux agent + // env.PATH = "/Applications/MATLAB_R2023b.app/bin:${env.PATH}" // macOS agent + runMATLABCommand(command: 'disp("Hello World!")') } ``` ### Use the `runMATLABBuild` Step -Use the `runMATLABBuild` step in your pipeline to run a build using the [MATLAB build tool](https://www.mathworks.com/help/matlab/matlab_prog/overview-of-matlab-build-tool.html). You can use this step to run the tasks specified in a file named `buildfile.m` in the root of your repository. To use the `runMATLABBuild` step, you need MATLAB R2022b or a later release. +Use the `runMATLABBuild` step in your pipeline to run a build using the [MATLAB build tool](https://www.mathworks.com/help/matlab/matlab_prog/overview-of-matlab-build-tool.html). You can use this step to run the tasks specified in a file named `buildfile.m` in the root of your repository. To use the `runMATLABBuild` step, you need MATLAB R2022b or a later release. The step accepts optional inputs. -The `runMATLABBuild` step accepts an optional input. Use this input to specify the tasks to run. If you specify the step in your `Jenkinsfile` without an input (`runMATLABBuild()`), the plugin runs the default tasks in `buildfile.m` as well as all the tasks on which they depend. - -Input | Description +Input | Description ------------------------- | --------------- -`tasks` | (Optional) Tasks to execute. If you specify more than one task, use a space to separate them.
**Example:** `'mytask'`
**Example:** `'compile test'` +`tasks` | (Optional) Tasks to execute. If you specify more than one task, use a space to separate them. If you specify the step without this input (for example, `runMATLABBuild()`), the plugin runs the default tasks in `buildfile.m` as well as all the tasks on which they depend.
MATLAB exits with exit code 0 if the tasks run without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the stage to fail.
**Example:** `tasks: 'test'`
**Example:** `tasks: 'compile test'` +`startupOptions` | (Optional) MATLAB startup options. If you specify more than one option, use a space to separate them. For more information about startup options, see [Commonly Used Startup Options](https://www.mathworks.com/help/matlab/matlab_env/commonly-used-startup-options.html).
Using this input to specify the `-batch` or `-r` option is not supported.
**Example:** `startupOptions: '-nojvm'`
**Example:** `startupOptions: '-nojvm -logfile "output.log"'` + For example, in your `Jenkinsfile`, define a declarative pipeline to run a task named `mytask` as well as all the tasks on which it depends. @@ -252,7 +253,7 @@ pipeline { } ``` -You also can use `runMATLABBuild` in a scripted pipeline. +You can also use `runMATLABBuild` in a scripted pipeline. ```groovy // Scripted Pipeline @@ -261,8 +262,6 @@ node { } ``` -MATLAB exits with exit code 0 if the specified tasks run successfully. Otherwise, MATLAB terminates with a nonzero exit code, which causes the stage to fail. - When you use this step, a file named `buildfile.m` must be in the root of your repository. For more information about the build tool, see [Create and Run Tasks Using Build Tool](https://www.mathworks.com/help/matlab/matlab_prog/create-and-run-tasks-using-build-tool.html). @@ -295,27 +294,28 @@ node { } ``` -MATLAB exits with exit code 0 if the test suite runs successfully without any test failures. Otherwise, MATLAB terminates with a nonzero exit code, which causes the stage to fail. +MATLAB exits with exit code 0 if the test suite runs without any failures. Otherwise, MATLAB terminates with a nonzero exit code, which causes the stage to fail. You can customize the `runMATLABTests` step using optional inputs. For example, you can add source folders to the MATLAB search path, control which tests to run, and generate various artifacts. Input | Description ------------------------- | --------------- -`sourceFolder` | (Optional) Location of the folder containing source code, relative to the project root folder. The specified folder and its subfolders are added to the top of the MATLAB search path. If you specify `sourceFolder` and then generate coverage results, the plugin uses only the source code in the specified folder and its subfolders to generate the results. You can specify multiple folders using a comma-separated list.
**Example:** `['source']`
**Example:** `['source/folderA', 'source/folderB']` -`selectByFolder` | (Optional) Location of the folder used to select test suite elements, relative to the project root folder. To create a test suite, the plugin uses only the tests in the specified folder and its subfolders. You can specify multiple folders using a comma-separated list.
**Example:** `['test']`
**Example:** `['test/folderA', 'test/folderB']` -`selectByTag` | (Optional) Test tag used to select test suite elements. To create a test suite, the plugin uses only the test elements with the specified tag.
**Example:** `'FeatureA'` -`strict` | (Optional) Whether to apply strict checks when running tests, specified as `false` or `true`. By default, the value is `false`. If you specify a value of `true`, the plugin generates a qualification failure whenever a test issues a warning. -`useParallel` | (Optional) Whether to run tests in parallel, specified as `false` or `true`. By default, the value is `false` and tests run in serial. If the test runner configuration is suited for parallelization, you can specify a value of `true` to run tests in parallel. This input requires a Parallel Computing Toolbox license. -`outputDetail` | (Optional) Amount of output detail displayed for the test run, specified as `'None'`, `'Terse'`, `'Concise'`, `'Detailed'`, or `'Verbose'`. By default, the plugin displays failing and logged events at the `Detailed` level and test run progress at the `Concise` level. -`loggingLevel` | (Optional) Maximum verbosity level for logged diagnostics included for the test run, specified as `'None'`, `'Terse'`, `'Concise'`, `'Detailed'`, or `'Verbose'`. By default, the plugin includes diagnostics logged at the `Terse` level. -`testResultsPDF` | (Optional) Path to write the test results in PDF format. On macOS platforms, this input is supported in MATLAB R2020b and later.
**Example:** `'test-results/results.pdf'` -`testResultsTAP` | (Optional) Path to write the test results in TAP format.
**Example:** `'test-results/results.tap'` -`testResultsJUnit` | (Optional) Path to write the test results in JUnit XML format.
**Example:** `'test-results/results.xml'` -`testResultsSimulinkTest` | (Optional) Path to export Simulink Test Manager results in MLDATX format. This input requires a Simulink Test license and is supported in MATLAB R2019a and later.
**Example:** `'test-results/results.mldatx'` -`codeCoverageCobertura` | (Optional) Path to write the code coverage results in Cobertura XML format.
**Example:** `'code-coverage/coverage.xml'` -`modelCoverageCobertura` | (Optional) Path to write the model coverage results in Cobertura XML format. This input requires a Simulink Coverage license and is supported in MATLAB R2018b and later.
**Example:** `'model-coverage/coverage.xml'` - -For instance, define a declarative pipeline to run the tests in your MATLAB project, and then generate test results in JUnit XML format and code coverage results in Cobertura XML format at specified locations on the build agent. Generate the coverage results for only the code in the `source` folder in the root of your repository. +`sourceFolder` | (Optional) Location of the folder containing source code, relative to the project root folder. The specified folder and its subfolders are added to the top of the MATLAB search path. If you specify `sourceFolder` and then generate coverage results, the plugin uses only the source code in the specified folder and its subfolders to generate the results. You can specify multiple folders using a comma-separated list.
**Example:** `sourceFolder: ['source']`
**Example:** `sourceFolder: ['source/folderA', 'source/folderB']` +`selectByFolder` | (Optional) Location of the folder used to select test suite elements, relative to the project root folder. To create a test suite, the plugin uses only the tests in the specified folder and its subfolders. You can specify multiple folders using a comma-separated list.
**Example:** `selectByFolder: ['test']`
**Example:** `selectByFolder: ['test/folderA', 'test/folderB']` +`selectByTag` | (Optional) Test tag used to select test suite elements. To create a test suite, the plugin uses only the test elements with the specified tag.
**Example:** `selectByTag: 'FeatureA'` +`strict` | (Optional) Option to apply strict checks when running tests, specified as `false` or `true`. By default, the value is `false`. If you specify a value of `true`, the plugin generates a qualification failure whenever a test issues a warning.
**Example:** `strict: true` +`useParallel` | (Optional) Option to run tests in parallel, specified as `false` or `true`. By default, the value is `false` and tests run in serial. If the test runner configuration is suited for parallelization, you can specify a value of `true` to run tests in parallel. This input requires a Parallel Computing Toolbox license.
**Example:** `useParallel: true` +`outputDetail` | (Optional) Amount of output detail displayed for the test run, specified as `'None'`, `'Terse'`, `'Concise'`, `'Detailed'`, or `'Verbose'`. By default, the plugin displays failing and logged events at the `Detailed` level and test run progress at the `Concise` level.
**Example:** `outputDetail: 'Verbose'` +`loggingLevel` | (Optional) Maximum verbosity level for logged diagnostics included for the test run, specified as `'None'`, `'Terse'`, `'Concise'`, `'Detailed'`, or `'Verbose'`. By default, the plugin includes diagnostics logged at the `Terse` level.
**Example:** `loggingLevel: 'Detailed'` +`testResultsPDF` | (Optional) Path to write the test results in PDF format. On macOS platforms, this input is supported in MATLAB R2020b and later.
**Example:** `testResultsPDF: 'test-results/results.pdf'` +`testResultsTAP` | (Optional) Path to write the test results in TAP format.
**Example:** `testResultsTAP: 'test-results/results.tap'` +`testResultsJUnit` | (Optional) Path to write the test results in JUnit-style XML format.
**Example:** `testResultsJUnit: 'test-results/results.xml'` +`testResultsSimulinkTest` | (Optional) Path to export Simulink Test Manager results in MLDATX format. This input requires a Simulink Test license and is supported in MATLAB R2019a and later.
**Example:** `testResultsSimulinkTest: 'test-results/results.mldatx'` +`codeCoverageCobertura` | (Optional) Path to write the code coverage results in Cobertura XML format.
**Example:** `codeCoverageCobertura: 'code-coverage/coverage.xml'` +`modelCoverageCobertura` | (Optional) Path to write the model coverage results in Cobertura XML format. This input requires a Simulink Coverage license and is supported in MATLAB R2018b and later.
**Example:** `modelCoverageCobertura: 'model-coverage/coverage.xml'` +`startupOptions` | (Optional) MATLAB startup options. If you specify more than one option, use a space to separate them. For more information about startup options, see [Commonly Used Startup Options](https://www.mathworks.com/help/matlab/matlab_env/commonly-used-startup-options.html).
Using this input to specify the `-batch` or `-r` option is not supported.
**Example:** `startupOptions: '-nojvm'`
**Example:** `startupOptions: '-nojvm -logfile "output.log"'` + +For instance, define a declarative pipeline to run the tests in your MATLAB project, and then generate test results in JUnit-style XML format and code coverage results in Cobertura XML format at specified locations on the build agent. Generate the coverage results for only the code in the `source` folder in the root of your repository. ```groovy @@ -346,12 +346,12 @@ node { ``` ### Use the `runMATLABCommand` Step -Use the `runMATLABCommand` step in your pipeline to run MATLAB scripts, functions, and statements. You can use this step to customize your test run or execute any MATLAB commands. - -You must provide `runMATLABCommand` with a string that specifies the script, function, or statement you want to execute. If you specify more than one script, function, or statement, use a comma or semicolon to separate them. If you want to run a script or function, do not specify the file extension. +Use the `runMATLABCommand` step in your pipeline to run MATLAB scripts, functions, and statements. You can use this step to customize your test run or execute any MATLAB commands. The step requires an input and also accepts an optional input. -**Example:** `runMATLABCommand 'myscript'`
-**Example:** `runMATLABCommand 'results = runtests, assertSuccess(results);'` +Input | Description +------------------------- | --------------- +`command` | (Required) Script, function, or statement to execute. If the value of `command` is the name of a MATLAB script or function, do not specify the file extension. If you specify more than one script, function, or statement, use a comma or semicolon to separate them.
MATLAB exits with exit code 0 if the specified script, function, or statement executes without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the stage to fail. To fail the stage in certain conditions, use the [`assert`](https://www.mathworks.com/help/matlab/ref/assert.html) or [`error`](https://www.mathworks.com/help/matlab/ref/error.html) function.
**Example:** `command: 'myscript'`
**Example:** `command: 'results = runtests, assertSuccess(results);'` +`startupOptions` | (Optional) MATLAB startup options. If you specify more than one option, use a space to separate them. For more information about startup options, see [Commonly Used Startup Options](https://www.mathworks.com/help/matlab/matlab_env/commonly-used-startup-options.html).
Using this input to specify the `-batch` or `-r` option is not supported.
**Example:** `startupOptions: '-nojvm'`
**Example:** `startupOptions: '-nojvm -logfile "output.log"'` For example, in your `Jenkinsfile`, define a declarative pipeline to run a script named `myscript.m`. @@ -362,32 +362,30 @@ pipeline { stages { stage('Run MATLAB Command') { steps { - runMATLABCommand 'myscript' + runMATLABCommand(command: 'myscript') } } } } ``` -You also can use `runMATLABCommand` in a scripted pipeline. +You can also use `runMATLABCommand` in a scripted pipeline. ```groovy // Scripted Pipeline node { - runMATLABCommand 'myscript' + runMATLABCommand(command: 'myscript') } -``` - -MATLAB exits with exit code 0 if the specified script, function, or statement executes successfully without error. Otherwise, MATLAB terminates with a nonzero exit code, which causes the stage to fail. If you properly react to the resulting MATLAB execution exception, the remaining stages of your pipeline can still run, and your build can succeed. Otherwise, Jenkins terminates the build in the current stage and marks it as a failure. +``` -When you use the `runMATLABCommand` step, all the required files must be on the MATLAB search path. If your script or function is not in the root of your repository, you can use the [`addpath`](https://www.mathworks.com/help/matlab/ref/addpath.html), [`cd`](https://www.mathworks.com/help/matlab/ref/cd.html), or [`run`](https://www.mathworks.com/help/matlab/ref/run.html) functions to ensure that it is on the path when invoked. For example, to run `myscript.m` in a folder named `myfolder` located in the root of the repository, you can specify the `runMATLABCommand` step like this: +When you use the `runMATLABCommand` step, all the required files must be on the MATLAB search path. If your script or function is not in the root of your repository, you can use the [`addpath`](https://www.mathworks.com/help/matlab/ref/addpath.html), [`cd`](https://www.mathworks.com/help/matlab/ref/cd.html), or [`run`](https://www.mathworks.com/help/matlab/ref/run.html) function to ensure that it is on the path when invoked. For example, to run `myscript.m` in a folder named `myfolder` located in the root of the repository, you can specify the `runMATLABCommand` step like this: -`runMATLABCommand 'addpath("myfolder"), myscript'` +`runMATLABCommand(command: 'addpath("myfolder"), myscript')` -## Use MATLAB in Matrix Build +### Use MATLAB in Matrix Build Like multi-configuration projects, you can use MATLAB as part of a [matrix](https://www.jenkins.io/doc/book/pipeline/syntax/#declarative-matrix) build in pipeline projects. For example, you can define a pipeline to run your test suite on different platforms or against different versions of MATLAB. -This example defines a declarative pipeline to run your MATLAB code and generate artifacts using MATLAB R2021b, R2022a, and R2022b. The pipeline has a `matrix` block to define the possible name-value combinations that should run in parallel. +This example defines a declarative pipeline to run your MATLAB code and generate artifacts using MATLAB R2022b, R2023a, and R2023b. The pipeline has a `matrix` block to define the possible name-value combinations that should run in parallel. ```groovy // Declarative Pipeline @@ -403,13 +401,13 @@ pipeline { axes { axis { name 'MATLAB_VERSION' - values 'R2021b', 'R2022a', 'R2022b' + values 'R2022b', 'R2023a', 'R2023b' } } stages { stage('Run MATLAB commands') { steps { - runMATLABCommand 'ver, pwd' + runMATLABCommand(command: 'ver, pwd') } } stage('Run MATLAB tests') { @@ -428,40 +426,40 @@ pipeline { ## Register MATLAB as Jenkins Tool When you run MATLAB code and Simulink models as part of your automated pipeline of tasks, Jenkins invokes MATLAB as an external program. When you configure your project, you can explicitly specify the MATLAB version that Jenkins should invoke by providing the path to the preferred MATLAB root folder. For example, you can use an `environment` block in your `Jenkinsfile` to specify a MATLAB root folder for your pipeline project. -Instead of specifying the path to the MATLAB root folder on a per-project basis, you can register a MATLAB version as a Jenkins tool, which makes it available to any project you configure in Jenkins. Once you have registered a MATLAB version as a tool, you no longer need to specify its root folder path within a project. Jenkins only needs the tool name to access the MATLAB version. +Instead of specifying the path to the MATLAB root folder on a per-project basis, you can register a MATLAB version as a Jenkins tool, which makes it available to any project you configure in Jenkins. Once you have registered a MATLAB version as a tool, you no longer need to specify its root folder path within a project. Jenkins needs only the tool name to access the MATLAB version. To register a MATLAB version as a Jenkins tool: -1) In your Jenkins interface, select **Manage Jenkins > Global Tool Configuration**. The **Global Tool Configuration** page opens where you can register different tools with Jenkins. -2) In the **MATLAB** section of the **Global Tool Configuration** page, click **Add MATLAB**. The section expands and lets you assign a name to your preferred MATLAB version and specify its installation location. +1) In your Jenkins interface, select **Manage Jenkins > Tools**. The **Tools** page opens where you can register different tools with Jenkins. +2) In the **MATLAB installations** section of the **Tools** page, click **Add MATLAB**. The section expands and lets you assign a name to your preferred MATLAB version and specify its installation location. 3) Specify the name you want to assign to the MATLAB version in the **Name** box, and enter the full path to its root folder in the **MATLAB root** box. To register the MATLAB version as a tool, do not select **Install automatically**. 4) To confirm your choices, click **Save** at the bottom of the page. -For example, register MATLAB R2022b as a Jenkins tool on your Windows local agent. +For example, register MATLAB R2023b as a Jenkins tool on your Windows local agent. -![matlab_tool](https://user-images.githubusercontent.com/48831250/217572380-21013b39-f85b-4709-a39d-3f862bb9d1eb.png) +![matlab_tool](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/50cb92d2-7b46-4bb7-822d-073e746e1d92) -If your Jenkins instance includes remote agents, you can register MATLAB as a tool on the remote agents using the tool name that you have specified on the local agent. For example, if you have registered MATLAB R2022b as a tool on your local agent, you can register the same MATLAB version installed on a remote agent as a tool on that agent. To register a MATLAB version as a Jenkins tool on a remote agent: +If your Jenkins instance includes remote agents, you can register MATLAB as a tool on the remote agents using the tool name that you specified on the local agent. For example, if you registered MATLAB R2023b as a tool on your local agent, you can register the same MATLAB version installed on a remote agent as a tool on that agent. To register a MATLAB version as a Jenkins tool on a remote agent: -1) Navigate to the **Node Properties** interface of the agent. You can access this interface by selecting **Manage Jenkins > Manage Nodes and Clouds**, following the link corresponding to the agent, and then selecting **Configure** on the left. +1) Navigate to the **Node Properties** interface of the agent. You can access this interface by selecting **Manage Jenkins > Nodes**, following the link corresponding to the agent, and then selecting **Configure** on the left. 2) Select **Tool Locations**. Then, select the tool name from the **Name** list. The list contains the names assigned to the registered MATLAB versions on the local agent. 3) In the **Home** box, enter the full path to the MATLAB root folder on the remote agent. 4) Click **Save** to confirm your choices. ### Use MATLAB as a Tool in Pipeline Project -To invoke MATLAB as a Jenkins tool using declarative pipeline syntax, use a `tools` block in your `Jenkinsfile`. To specify the tool in the block, use the `matlab` keyword followed by the name assigned to the tool on the **Global Tool Configuration** page. For example, run `myscript.m` using the MATLAB version that has been registered as a tool named R2022b. +To invoke MATLAB as a Jenkins tool using declarative pipeline syntax, use a `tools` block in your `Jenkinsfile`. To specify the tool in the block, use the `matlab` keyword followed by the name assigned to the tool on the **Tools** page. For example, run `myscript.m` using the MATLAB version that has been registered as a tool named R2023b. ```groovy // Declarative Pipeline pipeline { agent any tools { - matlab 'R2022b' + matlab 'R2023b' } stages { stage('Run MATLAB Command') { steps { - runMATLABCommand 'myscript' + runMATLABCommand(command: 'myscript') } } } @@ -475,17 +473,17 @@ If you define your pipeline using scripted pipeline syntax, use the `tool` keywo node { def matlabver stage('Run MATLAB Command') { - matlabver = tool 'R2022b' + matlabver = tool 'R2023b' if (isUnix()) { env.PATH = "${matlabver}/bin:${env.PATH}" // Linux or macOS agent } else { env.PATH = "${matlabver}\\bin;${env.PATH}" // Windows agent } - runMATLABCommand 'myscript' + runMATLABCommand(command: 'myscript') } } ``` -You also can invoke MATLAB as a Jenkins tool when you perform a matrix build in your pipeline project. This example uses three MATLAB versions (specified in an `axis` block using their tool names) to run a set of MATLAB commands and tests. +You can also invoke MATLAB as a Jenkins tool when you perform a matrix build in your pipeline project. This example uses three MATLAB versions (specified in an `axis` block using their tool names) to run a set of MATLAB commands and tests. ```groovy // Declarative Pipeline @@ -498,7 +496,7 @@ pipeline { axes { axis { name 'MATLAB_VERSION' - values 'R2021b', 'R2022a', 'R2022b' + values 'R2022b', 'R2023a', 'R2023b' } } tools { @@ -507,7 +505,7 @@ pipeline { stages { stage('Run MATLAB commands') { steps { - runMATLABCommand 'ver, pwd' + runMATLABCommand(command: 'ver, pwd') } } stage('Run MATLAB Tests') { diff --git a/examples/Run-MATLAB-Tests.md b/examples/Run-MATLAB-Tests.md index 5c9b4bab..e14bb7b4 100644 --- a/examples/Run-MATLAB-Tests.md +++ b/examples/Run-MATLAB-Tests.md @@ -5,13 +5,13 @@ This example shows how to run a suite of MATLAB® unit tests with Jenkins&tra * Add a build step to the project to run the tests and generate test and coverage artifacts. * Build the project and examine the test results and the generated artifacts. -The freestyle project runs the tests in the Times Table App MATLAB project (which requires R2019a or later). You can create a working copy of the project files and open the project in MATLAB by running this statement in the Command Window. +The freestyle project runs the tests in the Times Table App MATLAB project (which requires R2019a or later). You can create a working copy of the project files and open the project in MATLAB by running a statement in the Command Window. The statement to run depends on your MATLAB release: -``` -matlab.project.example.timesTable -``` +R2023a and Earlier | Starting in R2023b +-----------------------------------| ------------------------------------------------ +`matlab.project.example.timesTable`| `openExample("matlab/TimesTableProjectExample")` -For more information about the Times Table App example project, see [Explore an Example Project](https://www.mathworks.com/help/matlab/matlab_prog/explore-an-example-project.html). +For more information about the Times Table App project, see [Explore an Example Project](https://www.mathworks.com/help/matlab/matlab_prog/explore-an-example-project.html). ## Prerequisites To follow the steps in this example: @@ -24,37 +24,37 @@ To follow the steps in this example: Create a new project and configure it by following these steps: 1. In your Jenkins interface, select **New Item** on the left. A new page opens where you can choose the type of your project. Enter a project name, and then click **Freestyle project**. To confirm your choices, click **OK**. -![create_project](https://user-images.githubusercontent.com/48831250/217659170-43474c58-d6a1-44fa-bf72-eafe2aca49bb.png) +![create_project](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/8aa314b3-60fc-4534-bd49-223617ca0542) 2. On the project configuration page, in the **Source Code Management** section, specify the repository that hosts your tests. -![source_control](https://user-images.githubusercontent.com/48831250/217660122-eddabcd5-cab1-4c41-a175-3641457b6d2c.png) +![source_control](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/5befa3c5-6924-4abb-bc34-25ff3328ee47) 3. In the **Build Environment** section, select **Use MATLAB version** and specify the MATLAB version you want to use in the build. If your preferred MATLAB version is not listed under **Use MATLAB version**, enter the full path to its root folder in the **MATLAB root** box. -![build_environment](https://user-images.githubusercontent.com/48831250/217660546-65dc1045-2e4b-4e4b-a1cb-c4b0fedbdbc3.png) +![build_environment](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/c77220c0-a521-41ad-b0e4-76a6f0afce28) -4. In the **Build Steps** section, select **Add build step > Run MATLAB Tests**. Then, specify the artifacts to be generated in the project workspace. In this example, the plugin generates test results in JUnit XML format and code coverage results in Cobertura XML format. Furthermore, to generate the coverage results, the plugin uses only the code in the `source` folder located in the root of the repository. For more information about the build steps provided by the plugin, see [Plugin Configuration Guide](../CONFIGDOC.md). +4. In the **Build Steps** section, select **Add build step > Run MATLAB Tests**. Then, specify the artifacts to generate in the project workspace. In this example, the plugin generates test results in JUnit-style XML format and code coverage results in Cobertura XML format. Furthermore, to generate the coverage results, the plugin uses only the code in the `source` folder located in the root of the repository. For more information about the build steps provided by the plugin, see [Plugin Configuration Guide](../CONFIGDOC.md). -![run_matlab_tests](https://user-images.githubusercontent.com/48831250/217660935-7b6fbcda-5149-4863-983c-64360b8edd07.png) +![run_matlab_tests](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/2ef326b7-9b39-4068-83b9-011cebd52506) 5. In the **Post-build Actions** section, add two post-build actions to publish the JUnit-style test results and the Cobertura code coverage results. For each artifact, provide the path to the report. -![post_build](https://user-images.githubusercontent.com/48831250/217661749-c0ed8340-9fe8-4f88-82f9-f91c2737259d.png) +![post_build](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/d584a290-de93-4a9f-8061-00bc2a435c12) 6. Click **Save** to save the project configuration settings. You can access and modify your settings at a later stage by selecting **Configure** in the project interface, which displays the project name at the upper-left corner of the page. ## Run Tests and Inspect Artifacts -To build your freestyle project, click **Build Now** in the project interface. Jenkins triggers a build, assigns it a number under **Build History**, and runs the build. If the build succeeds, a green icon appears next to the build number. If the build fails, Jenkins adds a red icon. In this example, the build succeeds because all the tests in the Times Table App project pass. +To build your freestyle project, select **Build Now** in the project interface. Jenkins triggers a build, assigns it a number under **Build History**, and runs the build. In this example, the build succeeds because all the tests in the Times Table App project pass. -Navigate to the project workspace by clicking the **Workspace** icon in the project interface. The generated artifacts are in the `matlabTestArtifacts` folder of the workspace. +Navigate to the project workspace by selecting **Workspace** in the project interface. The generated artifacts are in the `matlabTestArtifacts` folder of the workspace. -![workspace](https://user-images.githubusercontent.com/48831250/217663519-d5a4c5bb-43e5-4ff4-ae32-c4b5e1181da7.png) +![workspace](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/5195fb71-6f4f-4261-82c0-501ab953a079) -Click the **Status** icon in the project interface. You can access the published artifacts by clicking the **Latest Test Result** and **Coverage Report** links. For example, click the **Latest Test Result** link to view the published JUnit-style test results. On the test results page, click the **(root)** link in the **All Tests** table. The table expands and lists information for each of the test classes within the Times Table App project. +Select **Status** in the project interface. You can access the published artifacts by clicking the **Latest Test Result** and **Coverage Report** links. For example, click the **Latest Test Result** link to view the published JUnit-style test results. On the test results page, click the **(root)** link in the **All Tests** table. The table expands and lists information for each of the test classes within the Times Table App project. -![test_results](https://user-images.githubusercontent.com/48831250/217663985-14b433e2-7546-40b1-a2e3-34d56f1f11ff.png) +![test_results](https://github.com/mathworks/jenkins-matlab-plugin/assets/48831250/2dc5142d-b844-436c-8e9e-8f7b90c7b69e) ## See Also * [Plugin Configuration Guide](../CONFIGDOC.md)
-* [Explore an Example Project (MATLAB)](https://www.mathworks.com/help/matlab/matlab_prog/explore-an-example-project.html) \ No newline at end of file +* [Explore an Example Project (MATLAB)](https://www.mathworks.com/help/matlab/matlab_prog/explore-an-example-project.html) From 779becb0066238ed4ac13fb5ae913f026499bbda Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:42:38 +0530 Subject: [PATCH 02/20] [maven-release-plugin] prepare release matlab-2.11.0 --- pom.xml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index fd19a679..7a6608b8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 org.jenkins-ci.plugins @@ -10,7 +8,7 @@ matlab - 2.10.1-SNAPSHOT + 2.11.0 hpi @@ -48,7 +46,7 @@ scm:git:ssh://github.com/jenkinsci/matlab-plugin.git scm:git:ssh://git@github.com/jenkinsci/matlab-plugin.git http://github.com/jenkinsci/matlab-plugin - HEAD + matlab-2.11.0 From 3dd07b275fa03d2660501fdf8bf40d57e5cd17fd Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Mon, 23 Oct 2023 13:42:45 +0530 Subject: [PATCH 03/20] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7a6608b8..59cc4ea4 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ matlab - 2.11.0 + 2.11.1-SNAPSHOT hpi @@ -46,7 +46,7 @@ scm:git:ssh://github.com/jenkinsci/matlab-plugin.git scm:git:ssh://git@github.com/jenkinsci/matlab-plugin.git http://github.com/jenkinsci/matlab-plugin - matlab-2.11.0 + HEAD From de1e2644fd5d283c93b79b88b4530810e1f7d0db Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:15:04 +0530 Subject: [PATCH 04/20] Addressed Jenkins security issues --- src/main/java/com/mathworks/ci/MatlabBuilder.java | 1 + src/main/java/com/mathworks/ci/MatlabReleaseInfo.java | 11 +++++++++++ .../mathworks/ci/UseMatlabVersionBuildWrapper.java | 2 ++ 3 files changed, 14 insertions(+) diff --git a/src/main/java/com/mathworks/ci/MatlabBuilder.java b/src/main/java/com/mathworks/ci/MatlabBuilder.java index 424c2724..d755c9a2 100644 --- a/src/main/java/com/mathworks/ci/MatlabBuilder.java +++ b/src/main/java/com/mathworks/ci/MatlabBuilder.java @@ -150,6 +150,7 @@ public DescriptorExtensionList> get public FormValidation doCheckMatlabRoot(@QueryParameter String matlabRoot) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); setMatlabRoot(matlabRoot); List> listOfCheckMethods = new ArrayList>(); diff --git a/src/main/java/com/mathworks/ci/MatlabReleaseInfo.java b/src/main/java/com/mathworks/ci/MatlabReleaseInfo.java index 82458a50..0c86ada8 100644 --- a/src/main/java/com/mathworks/ci/MatlabReleaseInfo.java +++ b/src/main/java/com/mathworks/ci/MatlabReleaseInfo.java @@ -18,6 +18,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.collections.MapUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -25,6 +26,7 @@ import org.w3c.dom.NodeList; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.FilePath; +import org.xml.sax.SAXException; public class MatlabReleaseInfo { private FilePath matlabRoot; @@ -82,6 +84,15 @@ private Map getVersionInfoFromFile() throws MatlabVersionNotFoun FilePath versionFile = new FilePath(this.matlabRoot, VERSION_INFO_FILE); if(versionFile.exists()) { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + String FEATURE = null; + try{ + FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; + dbFactory.setFeature(FEATURE, true); + dbFactory.setXIncludeAware(false); + + } catch (ParserConfigurationException e) { + throw new MatlabVersionNotFoundException("Error parsing verify if XML is valid", e); + } DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(versionFile.read()); diff --git a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java index 5600cedd..23ad6304 100644 --- a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java +++ b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java @@ -17,6 +17,7 @@ import hudson.matrix.MatrixProject; import hudson.model.Computer; +import jenkins.model.Jenkins; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; @@ -139,6 +140,7 @@ public String getMatlabAxisWarning() { */ public FormValidation doCheckMatlabRootFolder(@QueryParameter String matlabRootFolder) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); List> listOfCheckMethods = new ArrayList>(); listOfCheckMethods.add(chkMatlabEmpty); From 9d7d0973d46ed0767768147050d6af95890be5cb Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Thu, 9 Nov 2023 16:56:15 +0530 Subject: [PATCH 05/20] Added Post action for Query parameters --- src/main/java/com/mathworks/ci/MatlabBuilder.java | 3 ++- .../java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java | 3 ++- src/main/resources/com/mathworks/ci/MatlabBuilder/config.jelly | 2 +- .../com/mathworks/ci/UseMatlabVersionBuildWrapper/config.jelly | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/mathworks/ci/MatlabBuilder.java b/src/main/java/com/mathworks/ci/MatlabBuilder.java index d755c9a2..de7649d2 100644 --- a/src/main/java/com/mathworks/ci/MatlabBuilder.java +++ b/src/main/java/com/mathworks/ci/MatlabBuilder.java @@ -43,6 +43,7 @@ import jenkins.model.Jenkins; import jenkins.tasks.SimpleBuildStep; import net.sf.json.JSONObject; +import org.kohsuke.stapler.verb.POST; public class MatlabBuilder extends Builder implements SimpleBuildStep { @@ -148,7 +149,7 @@ public DescriptorExtensionList> get * descriptor class. */ - + @POST public FormValidation doCheckMatlabRoot(@QueryParameter String matlabRoot) { Jenkins.get().checkPermission(Jenkins.ADMINISTER); setMatlabRoot(matlabRoot); diff --git a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java index 23ad6304..41c88393 100644 --- a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java +++ b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java @@ -32,6 +32,7 @@ import hudson.tasks.BuildWrapperDescriptor; import hudson.util.FormValidation; import jenkins.tasks.SimpleBuildWrapper; +import org.kohsuke.stapler.verb.POST; public class UseMatlabVersionBuildWrapper extends SimpleBuildWrapper { @@ -138,7 +139,7 @@ public String getMatlabAxisWarning() { * these methods are used to perform basic validation on UI elements associated with this * descriptor class. */ - + @POST public FormValidation doCheckMatlabRootFolder(@QueryParameter String matlabRootFolder) { Jenkins.get().checkPermission(Jenkins.ADMINISTER); List> listOfCheckMethods = diff --git a/src/main/resources/com/mathworks/ci/MatlabBuilder/config.jelly b/src/main/resources/com/mathworks/ci/MatlabBuilder/config.jelly index 7dc80e37..5a2cd6ae 100644 --- a/src/main/resources/com/mathworks/ci/MatlabBuilder/config.jelly +++ b/src/main/resources/com/mathworks/ci/MatlabBuilder/config.jelly @@ -4,7 +4,7 @@ Using this build step is not recommended and will be removed in a feature release. Use “Run MATLAB Tests” or “Run MATLAB Command” instead. - + diff --git a/src/main/resources/com/mathworks/ci/UseMatlabVersionBuildWrapper/config.jelly b/src/main/resources/com/mathworks/ci/UseMatlabVersionBuildWrapper/config.jelly index 9b4fb179..ce847e2e 100644 --- a/src/main/resources/com/mathworks/ci/UseMatlabVersionBuildWrapper/config.jelly +++ b/src/main/resources/com/mathworks/ci/UseMatlabVersionBuildWrapper/config.jelly @@ -18,7 +18,7 @@ - + From a7447e78def37edafb67b1205b1998bd97521475 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:32:33 +0530 Subject: [PATCH 06/20] changed permission --- src/main/java/com/mathworks/ci/MatlabBuilder.java | 2 +- .../java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mathworks/ci/MatlabBuilder.java b/src/main/java/com/mathworks/ci/MatlabBuilder.java index de7649d2..b1ec700c 100644 --- a/src/main/java/com/mathworks/ci/MatlabBuilder.java +++ b/src/main/java/com/mathworks/ci/MatlabBuilder.java @@ -151,7 +151,7 @@ public DescriptorExtensionList> get @POST public FormValidation doCheckMatlabRoot(@QueryParameter String matlabRoot) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS); setMatlabRoot(matlabRoot); List> listOfCheckMethods = new ArrayList>(); diff --git a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java index 41c88393..a57cdecd 100644 --- a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java +++ b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java @@ -141,7 +141,7 @@ public String getMatlabAxisWarning() { */ @POST public FormValidation doCheckMatlabRootFolder(@QueryParameter String matlabRootFolder) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); + Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS); List> listOfCheckMethods = new ArrayList>(); listOfCheckMethods.add(chkMatlabEmpty); From dc2af93639cbc1a2d3716f66416249f0cfa59eca Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:52:30 +0530 Subject: [PATCH 07/20] changed permission --- src/main/java/com/mathworks/ci/MatlabBuilder.java | 3 ++- .../java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mathworks/ci/MatlabBuilder.java b/src/main/java/com/mathworks/ci/MatlabBuilder.java index b1ec700c..4d846b55 100644 --- a/src/main/java/com/mathworks/ci/MatlabBuilder.java +++ b/src/main/java/com/mathworks/ci/MatlabBuilder.java @@ -8,6 +8,7 @@ * nikhil.bhoski@mathworks.in Date : 28/03/2018 (Initial draft) */ +import hudson.security.Permission; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -151,7 +152,7 @@ public DescriptorExtensionList> get @POST public FormValidation doCheckMatlabRoot(@QueryParameter String matlabRoot) { - Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS); + Jenkins.get().checkPermission(Permission.CONFIGURE); setMatlabRoot(matlabRoot); List> listOfCheckMethods = new ArrayList>(); diff --git a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java index a57cdecd..5bf7b44e 100644 --- a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java +++ b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java @@ -8,6 +8,7 @@ * */ +import hudson.security.Permission; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -141,7 +142,7 @@ public String getMatlabAxisWarning() { */ @POST public FormValidation doCheckMatlabRootFolder(@QueryParameter String matlabRootFolder) { - Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS); + Jenkins.get().checkPermission(Permission.CONFIGURE); List> listOfCheckMethods = new ArrayList>(); listOfCheckMethods.add(chkMatlabEmpty); From aac7d996ccae683e701c7e65c2f3d1702afd0ad0 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:34:32 +0530 Subject: [PATCH 08/20] changed permission --- src/main/java/com/mathworks/ci/MatlabBuilder.java | 9 +++++++-- .../com/mathworks/ci/UseMatlabVersionBuildWrapper.java | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/mathworks/ci/MatlabBuilder.java b/src/main/java/com/mathworks/ci/MatlabBuilder.java index 4d846b55..f6c7c18a 100644 --- a/src/main/java/com/mathworks/ci/MatlabBuilder.java +++ b/src/main/java/com/mathworks/ci/MatlabBuilder.java @@ -8,6 +8,7 @@ * nikhil.bhoski@mathworks.in Date : 28/03/2018 (Initial draft) */ +import hudson.model.Item; import hudson.security.Permission; import java.io.File; import java.io.IOException; @@ -20,6 +21,7 @@ import javax.annotation.Nonnull; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.ArrayUtils; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; @@ -151,8 +153,11 @@ public DescriptorExtensionList> get */ @POST - public FormValidation doCheckMatlabRoot(@QueryParameter String matlabRoot) { - Jenkins.get().checkPermission(Permission.CONFIGURE); + public FormValidation doCheckMatlabRoot(@QueryParameter String matlabRoot, @AncestorInPath Item item) { + if (item == null) { + return FormValidation.ok(); + } + item.checkPermission(Item.CONFIGURE); setMatlabRoot(matlabRoot); List> listOfCheckMethods = new ArrayList>(); diff --git a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java index 5bf7b44e..f39f5e3b 100644 --- a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java +++ b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java @@ -8,6 +8,7 @@ * */ +import hudson.model.Item; import hudson.security.Permission; import java.io.File; import java.io.IOException; @@ -19,6 +20,7 @@ import hudson.matrix.MatrixProject; import hudson.model.Computer; import jenkins.model.Jenkins; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; @@ -141,8 +143,11 @@ public String getMatlabAxisWarning() { * descriptor class. */ @POST - public FormValidation doCheckMatlabRootFolder(@QueryParameter String matlabRootFolder) { - Jenkins.get().checkPermission(Permission.CONFIGURE); + public FormValidation doCheckMatlabRootFolder(@QueryParameter String matlabRootFolder, @AncestorInPath Item item) { + if (item == null) { + return FormValidation.ok(); + } + item.checkPermission(Item.CONFIGURE); List> listOfCheckMethods = new ArrayList>(); listOfCheckMethods.add(chkMatlabEmpty); From 9363eaae8da6bc0eb4c70dd0d56d921b25832938 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:03:57 +0530 Subject: [PATCH 09/20] [maven-release-plugin] prepare release matlab-2.11.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 59cc4ea4..a5cb8104 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ matlab - 2.11.1-SNAPSHOT + 2.11.1 hpi @@ -46,7 +46,7 @@ scm:git:ssh://github.com/jenkinsci/matlab-plugin.git scm:git:ssh://git@github.com/jenkinsci/matlab-plugin.git http://github.com/jenkinsci/matlab-plugin - HEAD + matlab-2.11.1 From ef630389c422e1304721a8df4d233847ac3940b8 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:04:04 +0530 Subject: [PATCH 10/20] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a5cb8104..fcb38ad5 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ matlab - 2.11.1 + 2.11.2-SNAPSHOT hpi @@ -46,7 +46,7 @@ scm:git:ssh://github.com/jenkinsci/matlab-plugin.git scm:git:ssh://git@github.com/jenkinsci/matlab-plugin.git http://github.com/jenkinsci/matlab-plugin - matlab-2.11.1 + HEAD From 830887162d7879be9da224e3a2de366ef1b4eddb Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:53:22 +0530 Subject: [PATCH 11/20] Removed dead code and tests --- .../java/com/mathworks/ci/MatlabBuilder.java | 646 ------------------ .../com/mathworks/ci/MatlabBuilderTest.java | 575 ---------------- .../com/mathworks/ci/MatlabBuilderTester.java | 64 -- 3 files changed, 1285 deletions(-) delete mode 100644 src/main/java/com/mathworks/ci/MatlabBuilder.java delete mode 100644 src/test/java/com/mathworks/ci/MatlabBuilderTest.java delete mode 100644 src/test/java/com/mathworks/ci/MatlabBuilderTester.java diff --git a/src/main/java/com/mathworks/ci/MatlabBuilder.java b/src/main/java/com/mathworks/ci/MatlabBuilder.java deleted file mode 100644 index f6c7c18a..00000000 --- a/src/main/java/com/mathworks/ci/MatlabBuilder.java +++ /dev/null @@ -1,646 +0,0 @@ -package com.mathworks.ci; - -/* - * Copyright 2019-2020 The MathWorks, Inc. - * - * This is Matlab Builder class which describes the build step and its components. Builder displays - * Build step As "Run MATLAB Tests" under Build steps. Author : Nikhil Bhoski email : - * nikhil.bhoski@mathworks.in Date : 28/03/2018 (Initial draft) - */ - -import hudson.model.Item; -import hudson.security.Permission; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; -import javax.annotation.Nonnull; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang.ArrayUtils; -import org.kohsuke.stapler.AncestorInPath; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; -import hudson.DescriptorExtensionList; -import hudson.EnvVars; -import hudson.Extension; -import hudson.ExtensionPoint; -import hudson.FilePath; -import hudson.Launcher; -import hudson.Launcher.ProcStarter; -import hudson.model.AbstractProject; -import hudson.model.Describable; -import hudson.model.Descriptor; -import hudson.model.Result; -import hudson.model.Run; -import hudson.model.TaskListener; -import hudson.tasks.BuildStepDescriptor; -import hudson.tasks.Builder; -import hudson.util.FormValidation; -import hudson.util.FormValidation.Kind; -import jenkins.model.Jenkins; -import jenkins.tasks.SimpleBuildStep; -import net.sf.json.JSONObject; -import org.kohsuke.stapler.verb.POST; - -public class MatlabBuilder extends Builder implements SimpleBuildStep { - - private int buildResult; - private TestRunTypeList testRunTypeList; - private String matlabRoot; - private EnvVars env; - private MatlabReleaseInfo matlabRel; - private String nodeSpecificfileSeparator; - - @DataBoundConstructor - public MatlabBuilder() { - - - } - - - // Getter and Setters to access local members - - @DataBoundSetter - public void setMatlabRoot(String matlabRoot) { - this.matlabRoot = matlabRoot; - } - - @DataBoundSetter - public void setTestRunTypeList(TestRunTypeList testRunTypeList) { - this.testRunTypeList = testRunTypeList; - } - - public String getMatlabRoot() { - - return this.matlabRoot; - } - - public TestRunTypeList getTestRunTypeList() { - return this.testRunTypeList; - } - - private String getLocalMatlab() { - return this.env == null ? getMatlabRoot(): this.env.expand(getMatlabRoot()); - } - - private String getCustomMatlabCommand() { - return this.env == null ? this.getTestRunTypeList().getStringByName("customMatlabCommand") - : this.env.expand(this.getTestRunTypeList().getStringByName("customMatlabCommand")); - } - private void setEnv(EnvVars env) { - this.env = env; - } - - - @Extension - public static class MatlabDescriptor extends BuildStepDescriptor { - MatlabReleaseInfo rel; - String matlabRoot; - - public String getMatlabRoot() { - return matlabRoot; - } - - public void setMatlabRoot(String matlabRoot) { - this.matlabRoot = matlabRoot; - } - - // Overridden Method used to show the text under build dropdown - @Override - public String getDisplayName() { - // No name for this descriptor as its deprecated all the jobs will be - // automatically delegated to the new TestRun or Script builders. - return ""; - } - - @Override - public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { - save(); - return super.configure(req, formData); - } - - /* - * This is to identify which project type in jenkins this should be applicable.(non-Javadoc) - * - * @see hudson.tasks.BuildStepDescriptor#isApplicable(java.lang.Class) - * - * if it returns true then this build step will be applicable for all project type. - */ - @Override - public boolean isApplicable( - @SuppressWarnings("rawtypes") Class jobtype) { - return true; - } - - /* - * below descriptor will get the values of Runtype descriptors and will assign it to the - * dropdown list of config.jelly - */ - public DescriptorExtensionList> getTestRunTypeDescriptor() { - return Jenkins.getInstance().getDescriptorList(TestRunTypeList.class); - } - - /* - * Below methods with 'doCheck' prefix gets called by jenkins when this builder is loaded. - * these methods are used to perform basic validation on UI elements associated with this - * descriptor class. - */ - - @POST - public FormValidation doCheckMatlabRoot(@QueryParameter String matlabRoot, @AncestorInPath Item item) { - if (item == null) { - return FormValidation.ok(); - } - item.checkPermission(Item.CONFIGURE); - setMatlabRoot(matlabRoot); - List> listOfCheckMethods = - new ArrayList>(); - listOfCheckMethods.add(chkMatlabEmpty); - listOfCheckMethods.add(chkMatlabSupportsRunTests); - - return getFirstErrorOrWarning(listOfCheckMethods, matlabRoot); - } - - public FormValidation getFirstErrorOrWarning( - List> validations, String matlabRoot) { - if (validations == null || validations.isEmpty()) - return FormValidation.ok(); - for (Function val : validations) { - FormValidation validationResult = val.apply(matlabRoot); - if (validationResult.kind.compareTo(Kind.ERROR) == 0 - || validationResult.kind.compareTo(Kind.WARNING) == 0) { - return validationResult; - } - } - return FormValidation.ok(); - } - - Function chkMatlabEmpty = (String matlabRoot) -> { - if (matlabRoot.isEmpty()) { - return FormValidation.error(Message.getValue("Builder.matlab.root.empty.error")); - } - return FormValidation.ok(); - }; - - Function chkMatlabSupportsRunTests = (String matlabRoot) -> { - final MatrixPatternResolver resolver = new MatrixPatternResolver(matlabRoot); - if (!resolver.hasVariablePattern()) { - try { - FilePath matlabRootPath = new FilePath(new File(matlabRoot)); - rel = new MatlabReleaseInfo(matlabRootPath); - if (rel.verLessThan(MatlabBuilderConstants.BASE_MATLAB_VERSION_RUNTESTS_SUPPORT)) { - return FormValidation - .error(Message.getValue("Builder.matlab.test.support.error")); - } - } catch (MatlabVersionNotFoundException e) { - return FormValidation - .warning(Message.getValue("Builder.invalid.matlab.root.warning")); - } - } - return FormValidation.ok(); - }; - } - - /* - * Below abstract class is a describable class which holds the list of Runtype options available - * on UI dropdown - * - */ - - public static abstract class TestRunTypeList - implements ExtensionPoint, Describable { - public TestRunTypeList() { - - } - - // Below abstract methods provides access to the public values assigned to UI elements which - // are displayed based on dropdown option Each Runtype option class should implement below - // methods. - - public abstract boolean getBooleanByName(String memberName); - - public abstract String getStringByName(String memberName); - - @SuppressWarnings("unchecked") - public Descriptor getDescriptor() { - return Jenkins.getInstance().getDescriptor(getClass()); - } - } - - /* - * Creating type Descriptor class which acts as descriptor for each dropdown items on UI All - * FormValidation Method related to each dropdown options class should go in this descriptor - * class. - * - */ - - public static abstract class TestRunTypeDescriptor extends Descriptor { - MatlabReleaseInfo rel; - - /* - * Validation for Test artifact generator checkBoxes - */ - - public FormValidation doCheckTaCoberturaChkBx(@QueryParameter boolean taCoberturaChkBx) { - List> listOfCheckMethods = - new ArrayList>(); - if (taCoberturaChkBx) { - listOfCheckMethods.add(chkCoberturaSupport); - } - final String matlabRoot = Jenkins.getInstance() - .getDescriptorByType(MatlabDescriptor.class).getMatlabRoot(); - return Jenkins.getInstance().getDescriptorByType(MatlabDescriptor.class) - .getFirstErrorOrWarning(listOfCheckMethods, matlabRoot); - } - - Function chkCoberturaSupport = (String matlabRoot) -> { - FilePath matlabRootPath = new FilePath(new File(matlabRoot)); - rel = new MatlabReleaseInfo(matlabRootPath); - final MatrixPatternResolver resolver = new MatrixPatternResolver(matlabRoot); - if(!resolver.hasVariablePattern()) { - try { - if (rel.verLessThan(MatlabBuilderConstants.BASE_MATLAB_VERSION_COBERTURA_SUPPORT)) { - return FormValidation - .warning(Message.getValue("Builder.matlab.cobertura.support.warning")); - } - } catch (MatlabVersionNotFoundException e) { - return FormValidation.warning(Message.getValue("Builder.invalid.matlab.root.warning")); - } - } - - - return FormValidation.ok(); - }; - - public FormValidation doCheckTaModelCoverageChkBx(@QueryParameter boolean taModelCoverageChkBx) { - List> listOfCheckMethods = - new ArrayList>(); - if (taModelCoverageChkBx) { - listOfCheckMethods.add(chkModelCoverageSupport); - } - final String matlabRoot = Jenkins.getInstance() - .getDescriptorByType(MatlabDescriptor.class).getMatlabRoot(); - return Jenkins.getInstance().getDescriptorByType(MatlabDescriptor.class) - .getFirstErrorOrWarning(listOfCheckMethods, matlabRoot); - } - - Function chkModelCoverageSupport = (String matlabRoot) -> { - FilePath matlabRootPath = new FilePath(new File(matlabRoot)); - rel = new MatlabReleaseInfo(matlabRootPath); - final MatrixPatternResolver resolver = new MatrixPatternResolver(matlabRoot); - if(!resolver.hasVariablePattern()) { - try { - if (rel.verLessThan(MatlabBuilderConstants.BASE_MATLAB_VERSION_MODELCOVERAGE_SUPPORT)) { - return FormValidation - .warning(Message.getValue("Builder.matlab.modelcoverage.support.warning")); - } - } catch (MatlabVersionNotFoundException e) { - return FormValidation.warning(Message.getValue("Builder.invalid.matlab.root.warning")); - } - } - - - return FormValidation.ok(); - }; - - public FormValidation doCheckTaSTMResultsChkBx(@QueryParameter boolean taSTMResultsChkBx) { - List> listOfCheckMethods = - new ArrayList>(); - if (taSTMResultsChkBx) { - listOfCheckMethods.add(chkSTMResultsSupport); - } - final String matlabRoot = Jenkins.getInstance() - .getDescriptorByType(MatlabDescriptor.class).getMatlabRoot(); - return Jenkins.getInstance().getDescriptorByType(MatlabDescriptor.class) - .getFirstErrorOrWarning(listOfCheckMethods, matlabRoot); - } - - Function chkSTMResultsSupport = (String matlabRoot) -> { - FilePath matlabRootPath = new FilePath(new File(matlabRoot)); - rel = new MatlabReleaseInfo(matlabRootPath); - final MatrixPatternResolver resolver = new MatrixPatternResolver(matlabRoot); - if(!resolver.hasVariablePattern()) { - try { - if (rel.verLessThan(MatlabBuilderConstants.BASE_MATLAB_VERSION_EXPORTSTMRESULTS_SUPPORT)) { - return FormValidation - .warning(Message.getValue("Builder.matlab.exportstmresults.support.warning")); - } - } catch (MatlabVersionNotFoundException e) { - return FormValidation.warning(Message.getValue("Builder.invalid.matlab.root.warning")); - } - } - - - return FormValidation.ok(); - }; - - } - - /* - * Create class of type TestRunTypeList each options of dropdown should have associated class - * created. - * - */ - - public static class RunTestsAutomaticallyOption extends TestRunTypeList { - private boolean tatapChkBx; - private boolean taJunitChkBx; - private boolean taCoberturaChkBx; - private boolean taSTMResultsChkBx; - private boolean taModelCoverageChkBx; - private boolean taPDFReportChkBx; - - @DataBoundConstructor - public RunTestsAutomaticallyOption() { - super(); - } - - @DataBoundSetter - public void setTatapChkBx(boolean tatapChkBx) { - this.tatapChkBx = tatapChkBx; - } - - @DataBoundSetter - public void setTaJunitChkBx(boolean taJunitChkBx) { - this.taJunitChkBx = taJunitChkBx; - } - - @DataBoundSetter - public void setTaCoberturaChkBx(boolean taCoberturaChkBx) { - this.taCoberturaChkBx = taCoberturaChkBx; - } - - @DataBoundSetter - public void setTaSTMResultsChkBx(boolean taSTMResultsChkBx) { - this.taSTMResultsChkBx = taSTMResultsChkBx; - } - - @DataBoundSetter - public void setTaModelCoverageChkBx(boolean taModelCoverageChkBx) { - this.taModelCoverageChkBx = taModelCoverageChkBx; - } - - @DataBoundSetter - public void setTaPDFReportChkBx(boolean taPDFReportChkBx) { - this.taPDFReportChkBx = taPDFReportChkBx; - } - - public boolean getTatapChkBx() { - return tatapChkBx; - } - - public boolean getTaJunitChkBx() { - return taJunitChkBx; - } - - public boolean getTaCoberturaChkBx() { - return taCoberturaChkBx; - } - - public boolean getTaSTMResultsChkBx() { - return taSTMResultsChkBx; - } - - public boolean getTaModelCoverageChkBx() { - return taModelCoverageChkBx; - } - - public boolean getTaPDFReportChkBx() { - return taPDFReportChkBx; - } - - @Extension - public static final class DescriptorImpl extends TestRunTypeDescriptor { - @Override - public String getDisplayName() { - return Message.getValue("builder.matlab.automatictestoption.display.name"); - } - } - - @Override - public boolean getBooleanByName(String memberName) { - switch (memberName) { - case "tatapChkBx": - return this.getTatapChkBx(); - case "taJunitChkBx": - return this.getTaJunitChkBx(); - case "taCoberturaChkBx": - return this.getTaCoberturaChkBx(); - case "taSTMResultsChkBx": - return this.getTaSTMResultsChkBx(); - case "taModelCoverageChkBx": - return this.getTaModelCoverageChkBx(); - case "taPDFReportChkBx": - return this.getTaPDFReportChkBx(); - default: - return false; - } - } - - @Override - public String getStringByName(String memberName) { - return null; - } - } - - public static class RunTestsWithCustomCommandOption extends TestRunTypeList { - private String customMatlabCommand; - - @DataBoundConstructor - public RunTestsWithCustomCommandOption() { - super(); - } - - @DataBoundSetter - public void setCustomMatlabCommand(String customMatlabCommand) { - this.customMatlabCommand = customMatlabCommand; - } - - public String getCustomMatlabCommand() { - return this.customMatlabCommand; - } - - @Extension - public static final class DescriptorImpl extends TestRunTypeDescriptor { - @Override - public String getDisplayName() { - return Message.getValue("builder.matlab.customcommandoption.display.name"); - } - } - - @Override - public boolean getBooleanByName(String memberName) { - - return false; - } - - @Override - public String getStringByName(String memberName) { - switch (memberName) { - case "customMatlabCommand": - return this.getCustomMatlabCommand(); - default: - return null; - } - } - } - - @Override - public void perform(@Nonnull Run build, @Nonnull FilePath workspace, - @Nonnull Launcher launcher, @Nonnull TaskListener listener) - throws InterruptedException, IOException { - //Set the environment variable specific to the this build - setEnv(build.getEnvironment(listener)); - //Get node specific matlabroot to get matlab version information - FilePath nodeSpecificMatlabRoot = new FilePath(launcher.getChannel(),getLocalMatlab()); - matlabRel = new MatlabReleaseInfo(nodeSpecificMatlabRoot); - nodeSpecificfileSeparator = getNodeSpecificFileSeperator(launcher); - - // Invoke MATLAB command and transfer output to standard - // Output Console - - buildResult = execMatlabCommand(workspace, launcher, listener); - - if (buildResult != 0) { - build.setResult(Result.FAILURE); - } - } - - private synchronized int execMatlabCommand(FilePath workspace, Launcher launcher, - TaskListener listener) - throws IOException, InterruptedException { - ProcStarter matlabLauncher; - try { - matlabLauncher = launcher.launch().pwd(workspace).envs(this.env); - if (matlabRel.verLessThan(MatlabBuilderConstants.BASE_MATLAB_VERSION_BATCH_SUPPORT)) { - ListenerLogDecorator outStream = new ListenerLogDecorator(listener); - matlabLauncher = matlabLauncher.cmds(constructDefaultMatlabCommand(launcher.isUnix())).stderr(outStream); - } else { - matlabLauncher = matlabLauncher.cmds(constructMatlabCommandWithBatch()).stdout(listener); - } - - //Check the test run mode option selected by user and identify the target workspace to copy the scratch file. - final String testRunMode = this.getTestRunTypeList().getDescriptor().getId(); - - // Copy MATLAB scratch file into the workspace only if Automatic option is selected. - if (testRunMode.contains(MatlabBuilderConstants.AUTOMATIC_OPTION)) { - FilePath targetWorkspace = new FilePath(launcher.getChannel(), workspace.getRemote()); - copyMatlabScratchFileInWorkspace(MatlabBuilderConstants.MATLAB_RUNNER_RESOURCE, MatlabBuilderConstants.MATLAB_RUNNER_TARGET_FILE, targetWorkspace); - } - } catch (Exception e) { - listener.getLogger().println(e.getMessage()); - return 1; - } - return matlabLauncher.join(); - } - - public List constructMatlabCommandWithBatch() { - final String testRunMode = this.getTestRunTypeList().getDescriptor().getId(); - final String runCommand; - final List matlabDefaultArgs; - if (testRunMode.contains(MatlabBuilderConstants.AUTOMATIC_OPTION)) { - String matlabFunctionName = - FilenameUtils.removeExtension(Message.getValue(MatlabBuilderConstants.MATLAB_RUNNER_TARGET_FILE)); - runCommand = "exit(" + matlabFunctionName + "(" - + getInputArguments() + "))"; - } else { - - runCommand = getCustomMatlabCommand(); - } - - matlabDefaultArgs = - Arrays.asList(getLocalMatlab() + nodeSpecificfileSeparator + "bin" + nodeSpecificfileSeparator + "matlab", - "-batch", runCommand); - - return matlabDefaultArgs; - } - - public List constructDefaultMatlabCommand(boolean isLinuxLauncher) throws MatlabVersionNotFoundException { - final List matlabDefaultArgs = new ArrayList(); - Collections.addAll(matlabDefaultArgs, getPreRunnerSwitches()); - if (!isLinuxLauncher) { - matlabDefaultArgs.add("-noDisplayDesktop"); - } - Collections.addAll(matlabDefaultArgs, getRunnerSwitch()); - if (!isLinuxLauncher) { - matlabDefaultArgs.add("-wait"); - } - Collections.addAll(matlabDefaultArgs, getPostRunnerSwitches()); - return matlabDefaultArgs; - } - - - private String[] getPreRunnerSwitches() throws MatlabVersionNotFoundException { - String[] preRunnerSwitches = - {getLocalMatlab() + nodeSpecificfileSeparator + "bin" + nodeSpecificfileSeparator + "matlab", "-nosplash", - "-nodesktop"}; - if(!matlabRel.verLessThan(MatlabBuilderConstants.BASE_MATLAB_VERSION_NO_APP_ICON_SUPPORT)) { - preRunnerSwitches = (String[]) ArrayUtils.add(preRunnerSwitches, "-noAppIcon"); - } - return preRunnerSwitches; - } - - private String[] getPostRunnerSwitches() { - String[] postRunnerSwitch = {"-log"}; - return postRunnerSwitch; - } - - private String[] getRunnerSwitch() { - final String runCommand; - final String testRunMode = this.getTestRunTypeList().getDescriptor().getId(); - if (testRunMode.contains(MatlabBuilderConstants.AUTOMATIC_OPTION)) { - String matlabFunctionName = - FilenameUtils.removeExtension(Message.getValue(MatlabBuilderConstants.MATLAB_RUNNER_TARGET_FILE)); - runCommand = "try,exit(" + matlabFunctionName + "(" - + getInputArguments() - + ")),catch e,disp(getReport(e,'extended')),exit(1),end"; - } else { - runCommand = "try,eval('" + getCustomMatlabCommand().replaceAll("'","''") - + "'),catch e,disp(getReport(e,'extended')),exit(1),end,exit"; - } - - final String[] runnerSwitch = {"-r", runCommand}; - return runnerSwitch; - } - - private void copyMatlabScratchFileInWorkspace(String matlabRunnerResourcePath, - String matlabRunnerTarget, FilePath targetWorkspace) - throws IOException, InterruptedException { - final ClassLoader classLoader = getClass().getClassLoader(); - FilePath targetFile = - new FilePath(targetWorkspace, Message.getValue(matlabRunnerTarget)); - InputStream in = classLoader.getResourceAsStream(matlabRunnerResourcePath); - - targetFile.copyFrom(in); - } - - private String getNodeSpecificFileSeperator(Launcher launcher) { - if (launcher.isUnix()) { - return "/"; - } else { - return "\\"; - } - } - - // Concatenate the input arguments - private String getInputArguments() { - String pdfReport = MatlabBuilderConstants.PDF_REPORT + "," + getTestRunTypeList().getBooleanByName("taPDFReportChkBx"); - String tapResults = MatlabBuilderConstants.TAP_RESULTS + "," + getTestRunTypeList().getBooleanByName("tatapChkBx"); - String junitResults = MatlabBuilderConstants.JUNIT_RESULTS + "," + getTestRunTypeList().getBooleanByName("taJunitChkBx"); - String stmResults = MatlabBuilderConstants.STM_RESULTS + "," + getTestRunTypeList().getBooleanByName("taSTMResultsChkBx"); - String coberturaCodeCoverage = MatlabBuilderConstants.COBERTURA_CODE_COVERAGE + "," + getTestRunTypeList().getBooleanByName("taCoberturaChkBx"); - String coberturaModelCoverage = MatlabBuilderConstants.COBERTURA_MODEL_COVERAGE + "," + getTestRunTypeList().getBooleanByName("taModelCoverageChkBx"); - - String inputArgsToMatlabFcn = pdfReport + "," + tapResults + "," + junitResults + "," - + stmResults + "," + coberturaCodeCoverage + "," + coberturaModelCoverage; - - return inputArgsToMatlabFcn; - } - -} diff --git a/src/test/java/com/mathworks/ci/MatlabBuilderTest.java b/src/test/java/com/mathworks/ci/MatlabBuilderTest.java deleted file mode 100644 index 12213f0a..00000000 --- a/src/test/java/com/mathworks/ci/MatlabBuilderTest.java +++ /dev/null @@ -1,575 +0,0 @@ -package com.mathworks.ci; - - - -import static org.junit.Assert.assertFalse; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; -import com.gargoylesoftware.htmlunit.WebAssert; -import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.mathworks.ci.MatlabBuilder.RunTestsAutomaticallyOption; -import com.mathworks.ci.MatlabBuilder.RunTestsWithCustomCommandOption; -import hudson.EnvVars; -import hudson.FilePath; -import hudson.model.FreeStyleBuild; -import hudson.model.FreeStyleProject; -import hudson.model.Result; -import hudson.slaves.EnvironmentVariablesNodeProperty; - - -/* - * Copyright 2018-2020 The MathWorks, Inc. - * - * Test class for MatlabBuilder - * - * Author : Nikhil Bhoski email : nikhil.bhoski@mathworks.in Date : 28/03/2018 (Initial draft) - */ -public class MatlabBuilderTest { - - private static TestMessage messages; - private static String matlabExecutorAbsolutePath; - private FreeStyleProject project; - private MatlabBuilder matlabBuilder; - private static URL url; - private static String FileSeperator; - private static String VERSION_INFO_XML_FILE = "VersionInfo.xml"; - - @Rule - public JenkinsRule jenkins = new JenkinsRule(); - - @BeforeClass - public static void classSetup() throws URISyntaxException, IOException { - ClassLoader classLoader = MatlabBuilderTest.class.getClassLoader(); - if (!System.getProperty("os.name").startsWith("Win")) { - FileSeperator = "/"; - url = classLoader.getResource("com/mathworks/ci/linux/bin/matlab.sh"); - try { - matlabExecutorAbsolutePath = new File(url.toURI()).getAbsolutePath(); - - // Need to do this operation due to bug in maven Resource copy plugin [ - // https://issues.apache.org/jira/browse/MRESOURCES-132 ] - - ProcessBuilder pb = new ProcessBuilder("chmod", "755", matlabExecutorAbsolutePath); - pb.start(); - } catch (IOException e) { - e.printStackTrace(); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - } else { - FileSeperator = "\\"; - url = classLoader.getResource("com/mathworks/ci/win/bin/matlab.bat"); - matlabExecutorAbsolutePath = new File(url.toURI()).getAbsolutePath(); - } - messages = new TestMessage(); - } - - @Before - public void testSetup() throws IOException { - - this.project = jenkins.createFreeStyleProject(); - this.matlabBuilder = new MatlabBuilder(); - } - - @After - public void testTearDown() { - this.project = null; - this.matlabBuilder = null; - } - - private String getMatlabroot(String version) throws URISyntaxException { - String defaultVersionInfo = "versioninfo/R2017a/" + VERSION_INFO_XML_FILE; - String userVersionInfo = "versioninfo/"+version+"/" + VERSION_INFO_XML_FILE; - URL matlabRootURL = Optional.ofNullable(getResource(userVersionInfo)).orElseGet(() -> getResource(defaultVersionInfo)); - File matlabRoot = new File(matlabRootURL.toURI()); - return matlabRoot.getAbsolutePath().replace(FileSeperator + VERSION_INFO_XML_FILE,"").replace("R2017a",version); - } - - - /* - * Test Case to verify if Build step contains "Run MATLAB Tests" option.This - * Builder should not be displayed under buildstep. - */ - @Test - public void verifyBuildStepWithMATLABBuilder() throws Exception { - this.matlabBuilder.setMatlabRoot(""); - Assert.assertTrue(this.matlabBuilder.getDescriptor().getDisplayName().equalsIgnoreCase("")); - } - - - /* - * Test To verify MATLAB is launched with default arguments and with -batch when release - * supports -batch - */ - - @Test - public void verifyMATLABlaunchedWithDefaultArgumentsBatch() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - this.matlabBuilder.setTestRunTypeList(new RunTestsAutomaticallyOption()); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains("-batch", build); - jenkins.assertLogContains("exit(runMatlabTests", build); - Assert.assertEquals(3, matlabBuilder.constructMatlabCommandWithBatch().size()); - } - - /* - * Test To verify MATLAB is launched with default arguments and with -r when release supports -r - * on windows - */ - - @Test - public void verifyMATLABlaunchedWithDefaultArgumentsRWindows() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2017a")); - this.matlabBuilder.setTestRunTypeList(new RunTestsAutomaticallyOption()); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains("-r", build); - jenkins.assertLogContains("try,exit(runMatlabTests", build); - Assert.assertEquals(9, matlabBuilder.constructDefaultMatlabCommand(false).size()); - } - - /* - * Test To verify MATLAB is launched with default arguments and with -r when release supports -r - * on Linux - */ - - @Test - public void verifyMATLABlaunchedWithDefaultArgumentsRLinux() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2017a")); - this.matlabBuilder.setTestRunTypeList(new RunTestsAutomaticallyOption()); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains("-r", build); - jenkins.assertLogContains("try,exit(runMatlabTests", build); - Assert.assertEquals(7, matlabBuilder.constructDefaultMatlabCommand(true).size()); - } - - /* - * Test to verify if job fails when invalid MATLAB path is provided and Exception is thrown - */ - - @Test - public void verifyBuilderFailsForInvalidMATLABPath() throws Exception { - this.matlabBuilder.setMatlabRoot("/fake/matlabroot/that/does/not/exist"); - project.getBuildersList().add(this.matlabBuilder); - - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertBuildStatus(Result.FAILURE, build); - } - - /* - * Test to verify if Build FAILS when matlab test fails - */ - - @Test - public void verifyBuildFailureWhenMatlabException() throws Exception { - MatlabBuilderTester tester = new MatlabBuilderTester(getMatlabroot("R2018b"), - matlabExecutorAbsolutePath, "-positiveFail"); - tester.setTestRunTypeList(new RunTestsAutomaticallyOption()); - project.getBuildersList().add(tester); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertBuildStatus(Result.FAILURE, build); - } - - /* - * Test to verify if MATLAB gets invoked and job sets to UNSTABLE when valid matlabroot is - * provided and all test passed. - */ - @Test - public void verifyMatlabInvokedWithValidExecutable() throws Exception { - MatlabBuilderTester tester = new MatlabBuilderTester(getMatlabroot("R2018b"), - matlabExecutorAbsolutePath, "-positive"); - tester.setTestRunTypeList(new RunTestsAutomaticallyOption()); - project.getBuildersList().add(tester); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertBuildStatus(Result.SUCCESS, build); - jenkins.assertLogContains(messages.getMatlabInvokesPositive(), build); - - } - - /* - * Test to verify MATLAB executable path is same as provide by user - */ - - @Test - public void verifyMatlabPointsToValidExecutable() throws Exception { - MatlabBuilderTester tester = new MatlabBuilderTester(getMatlabroot("R2018b"), - matlabExecutorAbsolutePath, "-positive"); - project.getBuildersList().add(tester); - tester.setTestRunTypeList(new RunTestsAutomaticallyOption()); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertBuildStatus(Result.SUCCESS, build); - jenkins.assertLogContains(matlabExecutorAbsolutePath, build); - } - - /* - * Test to verify Build is set to FAILED when test fails - * - */ - - @Test - public void verifyBuildStatusWhenTestFails() throws Exception { - MatlabBuilderTester tester = new MatlabBuilderTester(getMatlabroot("R2018b"), - matlabExecutorAbsolutePath, "failTests"); - project.getBuildersList().add(tester); - - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertBuildStatus(Result.FAILURE, build); - } - - /* - * Tests to verify if verLessThan() method compares values appropriately. - */ - - @Test - public void verifyVerlessThan() throws Exception { - FilePath matlabRoot = new FilePath(new File(getMatlabroot("R2017a"))); - MatlabReleaseInfo rel = new MatlabReleaseInfo(matlabRoot); - - // verLessthan() will check all the versions against 9.2 which is version of R2017a - assertFalse(rel.verLessThan(9.1)); - assertFalse(rel.verLessThan(9.0)); - assertFalse(rel.verLessThan(9.2)); - Assert.assertTrue(rel.verLessThan(9.9)); - Assert.assertTrue(rel.verLessThan(10.1)); - } - - /* - * Test to verify if plugin invokes MATLAB with custom MATLAB command when Custom MATLAB command - * option is selected. for -r option - */ - - @Test - public void verifyCustomCommandInvoked() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2017a")); - RunTestsWithCustomCommandOption runOption = new RunTestsWithCustomCommandOption(); - runOption.setCustomMatlabCommand("runtests"); - this.matlabBuilder.setTestRunTypeList(runOption); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains("-r", build); - jenkins.assertLogContains("try,eval", build); - } - - /* - * Test to verify if plugin invokes MATLAB with custom MATLAB command when Custom MATLAB command - * option is selected. for -batch option - */ - - @Test - public void verifyCustomCommandInvokedForBatchMode() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - RunTestsWithCustomCommandOption runOption = new RunTestsWithCustomCommandOption(); - runOption.setCustomMatlabCommand("runtests"); - this.matlabBuilder.setTestRunTypeList(runOption); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains("-batch", build); - jenkins.assertLogContains("runtests", build); - } - - /* - * Test to verify if Automatic option passes appropriate test atrtifact values. - */ - - @Test - public void verifyRunTestAutomaticallyIsDefault() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - FreeStyleBuild build = getBuildforRunTestAutomatically(); - jenkins.assertLogContains("-batch", build); - jenkins.assertLogContains("\'PDFReport\',true,\'TAPResults\',true," + - "\'JUnitResults\',true,\'SimulinkTestResults\',true," + - "\'CoberturaCodeCoverage\',true,\'CoberturaModelCoverage\',true", build); - } - - /* - * Test to verify if MATALB scratch file is generated in workspace for Automatic option. - */ - @Test - public void verifyMATLABscratchFileGeneratedForAutomaticOption() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - this.matlabBuilder.setTestRunTypeList(new RunTestsAutomaticallyOption()); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - File matlabRunner = new File(build.getWorkspace() + File.separator + "runMatlabTests.m"); - Assert.assertTrue(matlabRunner.exists()); - } - - /* - * Test to verify if MATALB scratch file is not generated in workspace for Custom option. - */ - @Test - public void verifyMATLABscratchFileGeneratedForCustomOption() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - this.matlabBuilder.setTestRunTypeList(new RunTestsWithCustomCommandOption()); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - File matlabRunner = new File(build.getWorkspace() + File.separator + "runMatlabTests.m"); - Assert.assertFalse(matlabRunner.exists()); - } - - /* - * Test to verify default value of getStringByName() when Automatic test mode. - */ - - @Test - public void verifyDefaultValueOfgetStringByName() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - RunTestsAutomaticallyOption runOption = new RunTestsAutomaticallyOption(); - Assert.assertNull(runOption.getStringByName("fakeChkBox")); - } - - /* - * Test to verify default value of getBooleanByName() when Custom test mode. - */ - - @Test - public void verifyDefaultValueOfgetBooleanByName() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - RunTestsWithCustomCommandOption runOption = new RunTestsWithCustomCommandOption(); - Assert.assertFalse(runOption.getBooleanByName("fakeCommand")); - } - - /* - * Test to verify when MATLAB version is older the R2017a - */ - - @Test - public void verifyMatlabVersionOlderThanR17a() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2016b")); - FreeStyleBuild build = getBuildforRunTestAutomatically(); - jenkins.assertLogContains("-r", build); - jenkins.assertLogContains("try,exit(", build); - } - - /* - * Test To verify if UI throws an error when MATLAB root is empty. - * - */ - - @Test - public void verifyEmptyMatlabRootError() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - WebAssert.assertTextPresent(page, TestMessage.getValue("Builder.matlab.root.empty.error")); - } - - /* - * Test To verify UI does throw error when in valid MATLAB root entered - * - */ - - @Test - public void verifyInvalidMatlabRootDisplaysError() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot("/fake/matlab/path"); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - WebAssert.assertTextPresent(page, TestMessage.getValue("Builder.invalid.matlab.root.warning")); - } - - /* - * Test To verify UI does not throw any error when valid MATLAB root entered - * - */ - - @Test - public void verifyValidMatlabRootDoesntDisplayError() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - WebAssert.assertTextNotPresent(page, TestMessage.getValue("Builder.invalid.matlab.root.warning")); - } - - /* - * Test To verify UI displays Cobertura Warning message when unsupported MATLAB version used. - * - */ - - @Test - public void verifyCoberturaWarning() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2017a")); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - HtmlCheckBoxInput coberturaChkBx = page.getElementByName("taCoberturaChkBx"); - coberturaChkBx.setChecked(true); - Thread.sleep(2000); - WebAssert.assertTextPresent(page, TestMessage.getValue("Builder.matlab.cobertura.support.warning")); - } - - /* - * Test to verify that UI displays model coverage warning message when unsupported MATLAB version is used. - * - */ - - @Test - public void verifyModelCoverageWarning() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018a")); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - HtmlCheckBoxInput modelCoverageChkBx = page.getElementByName("taModelCoverageChkBx"); - modelCoverageChkBx.setChecked(true); - Thread.sleep(2000); - WebAssert.assertTextPresent(page, TestMessage.getValue("Builder.matlab.modelcoverage.support.warning")); - } - - /* - * Test to verify that UI displays STM results warning message when unsupported MATLAB version is used. - * - */ - - @Test - public void verifySTMResultsWarning() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2018b")); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - HtmlCheckBoxInput stmResultsChkBx = page.getElementByName("taSTMResultsChkBx"); - stmResultsChkBx.setChecked(true); - Thread.sleep(2000); - WebAssert.assertTextPresent(page, TestMessage.getValue("Builder.matlab.exportstmresults.support.warning")); - } - - /* - * Test To verify UI displays Cobertura warning message when invalid MATLAB root entered. - * - */ - - @Test - public void verifyCoberturaError() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot("/fake/matlab/path"); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - HtmlCheckBoxInput coberturaChkBx = page.getElementByName("taCoberturaChkBx"); - coberturaChkBx.setChecked(true); - Thread.sleep(2000); - String pageText = page.asText(); - String filteredPageText = pageText.replaceFirst(TestMessage.getValue("Builder.invalid.matlab.root.warning"), ""); - Assert.assertTrue(filteredPageText.contains(TestMessage.getValue("Builder.invalid.matlab.root.warning"))); - } - - @Test - public void verifyInvalidMatlabWarningForModelCoverage() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot("/fake/matlab/path"); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - HtmlCheckBoxInput modelCoverageChkBx = page.getElementByName("taModelCoverageChkBx"); - modelCoverageChkBx.setChecked(true); - Thread.sleep(2000); - String pageText = page.asText(); - String filteredPageText = pageText.replaceFirst(TestMessage.getValue("Builder.invalid.matlab.root.warning"), ""); - Assert.assertTrue(filteredPageText.contains(TestMessage.getValue("Builder.invalid.matlab.root.warning"))); - } - - @Test - public void verifyInvalidMatlabWarningForSTMResults() throws Exception { - project.getBuildersList().add(this.matlabBuilder); - this.matlabBuilder.setMatlabRoot("/fake/matlab/path"); - HtmlPage page = jenkins.createWebClient().goTo("job/test0/configure"); - HtmlCheckBoxInput stmResultsChkBx = page.getElementByName("taSTMResultsChkBx"); - stmResultsChkBx.setChecked(true); - Thread.sleep(2000); - String pageText = page.asText(); - String filteredPageText = pageText.replaceFirst(TestMessage.getValue("Builder.invalid.matlab.root.warning"), ""); - Assert.assertTrue(filteredPageText.contains(TestMessage.getValue("Builder.invalid.matlab.root.warning"))); - } - - /* - * Test to verify if MatlabRoot field supports Jenkins environment variables. - * - */ - - @Test - public void verifyEnvVarSupportForMatlabRoot() throws Exception { - EnvironmentVariablesNodeProperty prop = new EnvironmentVariablesNodeProperty(); - EnvVars var = prop.getEnvVars(); - var.put("MATLAB", "R2017a"); - jenkins.jenkins.getGlobalNodeProperties().add(prop); - String mroot = getMatlabroot("R2017a"); - this.matlabBuilder.setMatlabRoot(mroot.replace("R2017a", "$MATLAB")); - this.matlabBuilder.setTestRunTypeList(new RunTestsAutomaticallyOption()); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains(mroot, build); - - } - - /* - * Test to verify if Custom command field supports Jenkins environment variables. - * - */ - - @Test - public void verifyEnvVarSupportForCustomCommandField() throws Exception { - EnvironmentVariablesNodeProperty prop = new EnvironmentVariablesNodeProperty(); - EnvVars var = prop.getEnvVars(); - var.put("TAG", "R2017a"); - jenkins.jenkins.getGlobalNodeProperties().add(prop); - String mroot = getMatlabroot("R2017a"); - this.matlabBuilder.setMatlabRoot(mroot); - RunTestsWithCustomCommandOption runOption = new RunTestsWithCustomCommandOption(); - this.matlabBuilder.setTestRunTypeList(runOption); - runOption.setCustomMatlabCommand("disp($TAG)"); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains("R2017a", build); - - } - - /* - * Test to verify -noAppIcon is not displayed for MATLAB version R2015a - */ - - @Test - public void verifyNoAppIconForR2015a() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2015a")); - FreeStyleBuild build = getBuildforRunTestAutomatically(); - jenkins.assertLogContains("-r", build); - jenkins.assertLogNotContains("-noAppIcon", build); - } - - /* - * Test to verify -noAppIcon is displayed for MATLAB version R2015b - */ - - @Test - public void verifyNoAppIconForR2015b() throws Exception { - this.matlabBuilder.setMatlabRoot(getMatlabroot("R2015b")); - FreeStyleBuild build = getBuildforRunTestAutomatically(); - jenkins.assertLogContains("-r", build); - jenkins.assertLogContains("-noAppIcon", build); - } - - /* - * Private helper methods for tests - */ - - private FreeStyleBuild getBuildforRunTestAutomatically() throws InterruptedException, ExecutionException { - RunTestsAutomaticallyOption runOption = new RunTestsAutomaticallyOption(); - runOption.setTaCoberturaChkBx(true); - runOption.setTaJunitChkBx(true); - runOption.setTatapChkBx(true); - runOption.setTaModelCoverageChkBx(true); - runOption.setTaSTMResultsChkBx(true); - runOption.setTaPDFReportChkBx(true); - this.matlabBuilder.setTestRunTypeList(runOption); - project.getBuildersList().add(this.matlabBuilder); - FreeStyleBuild build = project.scheduleBuild2(0).get(); - return build; - } - - private URL getResource(String resource) { - return MatlabBuilderTest.class.getClassLoader().getResource(resource); - } -} diff --git a/src/test/java/com/mathworks/ci/MatlabBuilderTester.java b/src/test/java/com/mathworks/ci/MatlabBuilderTester.java deleted file mode 100644 index cad8d16d..00000000 --- a/src/test/java/com/mathworks/ci/MatlabBuilderTester.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.mathworks.ci; - -/* - * Copyright 2018 The MathWorks, Inc. - * - * Tester class for MatlabBuilder - * - * Author : Nikhil Bhoski email : nikhil.bhoski@mathworks.in Date : 08/07/2018 (Initial draft) - */ - -import java.util.ArrayList; -import java.util.List; -import hudson.Extension; -import hudson.model.AbstractProject; -import hudson.tasks.BuildStepDescriptor; -import hudson.tasks.Builder; - -public class MatlabBuilderTester extends MatlabBuilder { - private String commandParameter; - private String matlabExecutorPath; - - public MatlabBuilderTester(String localMatlab, String matlabExecutorPath, - String customTestPointArgument) { - super(); - setMatlabRoot(localMatlab); - this.commandParameter = customTestPointArgument; - this.matlabExecutorPath = matlabExecutorPath; - } - - @Override - public List constructMatlabCommandWithBatch() { - return testMatlabCommand(); - } - - @Override - public List constructDefaultMatlabCommand(boolean isLinuxLauncher) { - return testMatlabCommand(); - } - - private List testMatlabCommand() { - List matlabDefaultArgs = new ArrayList(); - matlabDefaultArgs.add(this.matlabExecutorPath); - matlabDefaultArgs.add(this.commandParameter); - return matlabDefaultArgs; - } - - - @Extension - public static class Descriptor extends BuildStepDescriptor { - - @Override - public boolean isApplicable( - @SuppressWarnings("rawtypes") Class jobType) { - - return true; - } - - @Override - public String getDisplayName() { - - return null; - } - } -} From 8074db29a090588b1794b2602d03e83a7a058058 Mon Sep 17 00:00:00 2001 From: sameagen Date: Fri, 15 Dec 2023 11:30:10 -0500 Subject: [PATCH 12/20] Initial fix --- .../java/com/mathworks/ci/MatlabBuildStepExecution.java | 8 ++++---- .../java/com/mathworks/ci/MatlabCommandStepExecution.java | 8 ++++---- src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java | 8 ++++---- .../java/com/mathworks/ci/RunMatlabCommandBuilder.java | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java b/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java index aeca1fe8..6f554c29 100644 --- a/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java +++ b/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java @@ -68,12 +68,12 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace); // Create MATLAB script - createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, workspace, listener); + createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, listener); ProcStarter matlabLauncher; try { matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, - "cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'); "+ uniqueBuildFile, startupOptions, uniqueTmpFldrName); + "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"')); "+ uniqueBuildFile, startupOptions, uniqueTmpFldrName); listener.getLogger() .println("#################### Starting command output ####################"); return matlabLauncher.pwd(workspace).join(); @@ -86,7 +86,7 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, } } - private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { + private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, TaskListener listener) throws IOException, InterruptedException { // Create a new command runner script in the temp folder. final FilePath matlabBuildFile = @@ -98,7 +98,7 @@ private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqu cmd += " " + tasks; } final String matlabBuildFileContent = - "cd '" + workspace.getRemote().replaceAll("'", "''") + "';\n" + cmd; + "cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; // Display the commands on console output for users reference listener.getLogger() diff --git a/src/main/java/com/mathworks/ci/MatlabCommandStepExecution.java b/src/main/java/com/mathworks/ci/MatlabCommandStepExecution.java index 49fece2c..f6a99f68 100644 --- a/src/main/java/com/mathworks/ci/MatlabCommandStepExecution.java +++ b/src/main/java/com/mathworks/ci/MatlabCommandStepExecution.java @@ -69,12 +69,12 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace); // Create MATLAB script - createMatlabScriptByName(uniqueTmpFolderPath, uniqueCommandFile, workspace, listener); + createMatlabScriptByName(uniqueTmpFolderPath, uniqueCommandFile, listener); ProcStarter matlabLauncher; try { matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, - "cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'); "+ uniqueCommandFile, startupOptions, uniqueTmpFldrName); + "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"')); "+ uniqueCommandFile, startupOptions, uniqueTmpFldrName); listener.getLogger() .println("#################### Starting command output ####################"); return matlabLauncher.pwd(workspace).join(); @@ -87,14 +87,14 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, } } - private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { + private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, TaskListener listener) throws IOException, InterruptedException { // Create a new command runner script in the temp folder. final FilePath matlabCommandFile = new FilePath(uniqueTmpFolderPath, uniqueScriptName + ".m"); final String cmd = getContext().get(EnvVars.class).expand(getCommand()); final String matlabCommandFileContent = - "cd '" + workspace.getRemote().replaceAll("'", "''") + "';\n" + cmd; + "cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; // Display the commands on console output for users reference listener.getLogger() diff --git a/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java b/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java index 3e7b4e34..45028232 100644 --- a/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java +++ b/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java @@ -117,12 +117,12 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace); // Create MATLAB script - createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, workspace, listener, envVars); + createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, listener, envVars); ProcStarter matlabLauncher; String options = getStartupOptions() == null ? "" : getStartupOptions().getOptions(); try { matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, - "cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"');"+ uniqueBuildFile, options, uniqueTmpFldrName); + "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'));"+ uniqueBuildFile, options, uniqueTmpFldrName); listener.getLogger() .println("#################### Starting command output ####################"); @@ -139,7 +139,7 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, } } - private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, FilePath workspace, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { + private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { // Create a new command runner script in the temp folder. final FilePath matlabCommandFile = @@ -152,7 +152,7 @@ private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqu } final String matlabCommandFileContent = - "cd '" + workspace.getRemote().replaceAll("'", "''") + "';\n" + cmd; + "cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; // Display the commands on console output for users reference listener.getLogger() diff --git a/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java b/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java index 33685293..6bbfb138 100644 --- a/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java +++ b/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java @@ -122,13 +122,13 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace); // Create MATLAB script - createMatlabScriptByName(uniqueTmpFolderPath, uniqueCommandFile, workspace, listener, envVars); + createMatlabScriptByName(uniqueTmpFolderPath, uniqueCommandFile, listener, envVars); ProcStarter matlabLauncher; String options = getStartupOptions() == null ? "" : getStartupOptions().getOptions(); try { matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, - "cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"');"+ uniqueCommandFile, options, uniqueTmpFldrName); + "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'));"+ uniqueCommandFile, options, uniqueTmpFldrName); listener.getLogger() .println("#################### Starting command output ####################"); @@ -145,14 +145,14 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, } } - private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, FilePath workspace, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { + private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { // Create a new command runner script in the temp folder. final FilePath matlabCommandFile = new FilePath(uniqueTmpFolderPath, uniqueScriptName + ".m"); final String cmd = envVars.expand(getMatlabCommand()); final String matlabCommandFileContent = - "cd '" + workspace.getRemote().replaceAll("'", "''") + "';\n" + cmd; + "cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; // Display the commands on console output for users reference listener.getLogger() From 2f875aa6266013c444f62553ebd54dd52e20c884 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:42:46 +0530 Subject: [PATCH 13/20] Added support for build visualization (Latest) (#268) * Added support for build visualization * Fixed findbug issues * Fixed findbug issues * Fixed findbug issues * Deleted failuresummary.jelly * Refactored tests * Refactored jelly file * Updated as per review comments * updated jelly as per review comment * Using overide plugin method to generate json file * Updated as per review comment * Updated as per review comment * Added log updater plugin * Added log link support * Fixed spotbug issue * Renamed plugin packages * Changed data types for artifact data * empty * Fixed test failure * Fixed Merge conflicts * Fixed spotbug issue * Updated as per review comments * Updated as per review comments * Updated as per review comments * Updated as per review comments --- pom.xml | 6 + .../com/mathworks/ci/BuildArtifactAction.java | 220 +++++++++++++++++ .../com/mathworks/ci/BuildArtifactData.java | 55 +++++ .../mathworks/ci/BuildConsoleAnnotator.java | 83 +++++++ .../com/mathworks/ci/BuildTargetNote.java | 37 +++ .../mathworks/ci/RunMatlabBuildBuilder.java | 86 ++++++- .../+ciplugins/+jenkins/BuildReportPlugin.m | 25 ++ .../+jenkins/TaskRunProgressPlugin.m | 13 + .../+ciplugins/+jenkins/getDefaultPlugins.m | 14 ++ .../ci/BuildArtifactAction/index.jelly | 91 +++++++ .../ci/BuildArtifactAction/summary.jelly | 16 ++ .../mathworks/ci/BuildArtifactActionTest.java | 233 ++++++++++++++++++ .../ci/RunMatlabBuildBuilderTest.java | 5 +- .../buildArtifacts.t2/buildArtifact.json | 9 + .../buildArtifacts/t1/buildArtifact.json | 25 ++ 15 files changed, 912 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/mathworks/ci/BuildArtifactAction.java create mode 100644 src/main/java/com/mathworks/ci/BuildArtifactData.java create mode 100644 src/main/java/com/mathworks/ci/BuildConsoleAnnotator.java create mode 100644 src/main/java/com/mathworks/ci/BuildTargetNote.java create mode 100644 src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m create mode 100644 src/main/resources/+ciplugins/+jenkins/TaskRunProgressPlugin.m create mode 100644 src/main/resources/+ciplugins/+jenkins/getDefaultPlugins.m create mode 100644 src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly create mode 100644 src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly create mode 100644 src/test/java/com/mathworks/ci/BuildArtifactActionTest.java create mode 100644 src/test/resources/buildArtifacts.t2/buildArtifact.json create mode 100644 src/test/resources/buildArtifacts/t1/buildArtifact.json diff --git a/pom.xml b/pom.xml index fcb38ad5..dfd69040 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,12 @@ + + + com.googlecode.json-simple + json-simple + 1.1.1 + org.jenkins-ci.plugins diff --git a/src/main/java/com/mathworks/ci/BuildArtifactAction.java b/src/main/java/com/mathworks/ci/BuildArtifactAction.java new file mode 100644 index 00000000..11a751f0 --- /dev/null +++ b/src/main/java/com/mathworks/ci/BuildArtifactAction.java @@ -0,0 +1,220 @@ +package com.mathworks.ci; + +import hudson.FilePath; +import hudson.model.Action; +import hudson.model.Run; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.CheckForNull; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + + +public class BuildArtifactAction implements Action { + private Run build; + private FilePath workspace; + private int totalCount; + private int skipCount; + private int failCount; + private static final String ROOT_ELEMENT = "taskDetails"; + private static final String BUILD_ARTIFACT_FILE = "buildArtifact.json"; + + public BuildArtifactAction(Run build, FilePath workspace) { + this.build = build; + this.workspace = workspace; + + // Setting the counts of task when Action is created. + try{ + setCounts(); + } catch (ParseException e) { + throw new RuntimeException(e); + } catch (InterruptedException e){ + throw new RuntimeException(e); + } + } + + @CheckForNull + @Override + public String getIconFileName() { + return "document.png"; + } + + @CheckForNull + @Override + public String getDisplayName() { + return "MATLAB Build Results"; + } + + @CheckForNull + @Override + public String getUrlName() { + return "buildresults"; + } + + public List getBuildArtifact() throws ParseException, InterruptedException, IOException { + List artifactData = new ArrayList(); + FilePath fl = new FilePath(new File(build.getRootDir().getAbsolutePath() + "/" + BUILD_ARTIFACT_FILE)); + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(fl.toURI())), "UTF-8")) { + Object obj = new JSONParser().parse(reader); + JSONObject jo = (JSONObject) obj; + if (jo.get(ROOT_ELEMENT) instanceof JSONArray) { + JSONArray ja = (JSONArray) jo.get(ROOT_ELEMENT); + Iterator itr2 = ja.iterator(); + Iterator itr1; + while (itr2.hasNext()) { + BuildArtifactData data = new BuildArtifactData(); + itr1 = ((Map) itr2.next()).entrySet().iterator(); + while (itr1.hasNext()) { + Entry pair = itr1.next(); + iterateAllTaskAttributes(pair, data); + } + artifactData.add(data); + } + } else { + Map ja = ((Map) jo.get(ROOT_ELEMENT)); + Iterator itr1 = ja.entrySet().iterator(); + BuildArtifactData data = new BuildArtifactData(); + while (itr1.hasNext()) { + Entry pair = itr1.next(); + iterateAllTaskAttributes(pair, data); + } + artifactData.add(data); + } + } catch (IOException e) { + throw new IOException(e.getLocalizedMessage()); + } + return artifactData; + } + + public void setTotalcount(int totalCount) { + this.totalCount = totalCount; + } + + public void setSkipCount(int skipCount) { + this.skipCount = skipCount; + } + + public int getTotalCount() { + return this.totalCount; + } + + public int getFailCount() { + return this.failCount; + } + + public void setFailCount(int failCount) { + this.failCount = failCount; + } + + public int getSkipCount() { + return this.skipCount; + } + + public Run getOwner() { + return this.build; + } + + /** + * @param owner the owner to set + */ + public void setOwner(Run owner) { + this.build = owner; + } + + public FilePath getWorkspace() { + return this.workspace; + } + + private void setCounts() throws InterruptedException, ParseException { + List artifactData = new ArrayList(); + FilePath fl = new FilePath(new File(build.getRootDir().getAbsolutePath() + "/" + BUILD_ARTIFACT_FILE)); + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(fl.toURI())), "UTF-8")) { + Object obj = new JSONParser().parse(reader); + JSONObject jo = (JSONObject) obj; + + // getting taskDetails + if (jo.get(ROOT_ELEMENT) instanceof JSONArray) { + JSONArray ja = (JSONArray) jo.get(ROOT_ELEMENT); + Iterator itr2 = ja.iterator(); + Iterator itr1; + while (itr2.hasNext()) { + BuildArtifactData data = new BuildArtifactData(); + itr1 = ((Map) itr2.next()).entrySet().iterator(); + while (itr1.hasNext()) { + Entry pair = itr1.next(); + iterateFailedSkipped(pair, data); + } + artifactData.add(data); + setTotalcount(artifactData.size()); + } + } else { + Map ja = ((Map) jo.get(ROOT_ELEMENT)); + Iterator itr1 = ja.entrySet().iterator(); + BuildArtifactData data = new BuildArtifactData(); + while (itr1.hasNext()) { + Entry pair = itr1.next(); + iterateFailedSkipped(pair, data); + } + artifactData.add(data); + setTotalcount(artifactData.size()); + } + } catch (IOException e) { + e.printStackTrace(); + } + + // Update the FAILED and SKIPPED task count + int failCount = 0; + int skipCount = 0; + for (BuildArtifactData data : artifactData) { + if (data.getTaskFailed()) { + failCount = failCount + 1; + } else if (data.getTaskSkipped()) { + skipCount = skipCount + 1; + } + } + // Set count for each failed and skipped tasks + setFailCount(failCount); + setSkipCount(skipCount); + } + + private void iterateAllTaskAttributes(Entry pair, BuildArtifactData data) { + // Iterates across all task attributes and updates + String key = pair.getKey().toString(); + switch(key.toLowerCase()){ + case "duration": + data.setTaskDuration(pair.getValue().toString()); + break; + case "name" : + data.setTaskName(pair.getValue().toString()); + break; + case "description": + data.setTaskDescription(pair.getValue().toString()); + break; + case "failed": + data.setTaskFailed((Boolean) pair.getValue()); + break; + case "skipped": + data.setTaskSkipped((Boolean) pair.getValue()); + break; + default : + break; + } + } + + private void iterateFailedSkipped(Entry pair, BuildArtifactData data) { + if (pair.getKey().toString().equalsIgnoreCase("failed")) { + data.setTaskFailed((Boolean) pair.getValue()); + } else if (pair.getKey().toString().equalsIgnoreCase("skipped")) { + data.setTaskSkipped((Boolean) pair.getValue()); + } + } +} diff --git a/src/main/java/com/mathworks/ci/BuildArtifactData.java b/src/main/java/com/mathworks/ci/BuildArtifactData.java new file mode 100644 index 00000000..d89a5ef9 --- /dev/null +++ b/src/main/java/com/mathworks/ci/BuildArtifactData.java @@ -0,0 +1,55 @@ +package com.mathworks.ci; + +public class BuildArtifactData { + + private String taskName; + private String taskDuration; + private boolean taskFailed; + + private String taskDescription; + private boolean taskSkipped; + + public BuildArtifactData() { + } + + + public String getTaskDuration() { + return this.taskDuration; + } + + public void setTaskDuration(String taskDuration) { + this.taskDuration = taskDuration; + } + + public String getTaskName() { + return this.taskName; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public boolean getTaskSkipped() { + return this.taskSkipped; + } + + public void setTaskSkipped(boolean taskSkipped) { + this.taskSkipped = taskSkipped; + } + + public boolean getTaskFailed() { + return this.taskFailed; + } + + public void setTaskFailed(boolean taskFailed) { + this.taskFailed = taskFailed; + } + + public String getTaskDescription() { + return this.taskDescription; + } + + public void setTaskDescription(String taskDescription) { + this.taskDescription = taskDescription; + } +} diff --git a/src/main/java/com/mathworks/ci/BuildConsoleAnnotator.java b/src/main/java/com/mathworks/ci/BuildConsoleAnnotator.java new file mode 100644 index 00000000..34fb6b70 --- /dev/null +++ b/src/main/java/com/mathworks/ci/BuildConsoleAnnotator.java @@ -0,0 +1,83 @@ +package com.mathworks.ci; + +import com.google.common.base.Charsets; +import hudson.console.ConsoleLogFilter; +import hudson.console.LineTransformationOutputStream; +import hudson.model.Run; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import jenkins.util.JenkinsJVM; + +public class BuildConsoleAnnotator extends LineTransformationOutputStream { + private final OutputStream out; + private final Charset charset; + + private final byte[][] antNotes; + + public BuildConsoleAnnotator(OutputStream out, Charset charset) { + this(out, charset, createBuildNotes()); + } + + private BuildConsoleAnnotator(OutputStream out, Charset charset, byte[][] antNotes) { + this.out = out; + this.charset = charset; + this.antNotes = antNotes; + } + + private static byte[][] createBuildNotes() { + JenkinsJVM.checkJenkinsJVM(); + try { + ByteArrayOutputStream targetNote = new ByteArrayOutputStream(); + new BuildTargetNote().encodeTo(targetNote); + ByteArrayOutputStream outcomeNote = new ByteArrayOutputStream(); + return new byte[][]{targetNote.toByteArray(), outcomeNote.toByteArray()}; + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + @Override + protected void eol(byte[] b, int len) throws IOException { + String line = charset.decode(ByteBuffer.wrap(b, 0, len)).toString(); + // trim off CR/LF from the end + line = trimEOL(line); + if (line.contains("[MATLAB-Build-")) + out.write(antNotes[0]); + + out.write(b, 0, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + super.close(); + out.close(); + } + + private static class ConsoleLogFilterImpl extends ConsoleLogFilter implements Serializable { + private static final long serialVersionUID = 1; + private byte[][] buildNotes = createBuildNotes(); + + //Taking care of old MATLAB build actions. + private Object readResolve() { + if (buildNotes == null) { + buildNotes = createBuildNotes(); + } + return this; + } + + @Override + public OutputStream decorateLogger(Run build, OutputStream logger) throws IOException, InterruptedException { + return new BuildConsoleAnnotator(logger, Charsets.UTF_8, buildNotes); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mathworks/ci/BuildTargetNote.java b/src/main/java/com/mathworks/ci/BuildTargetNote.java new file mode 100644 index 00000000..0367df4a --- /dev/null +++ b/src/main/java/com/mathworks/ci/BuildTargetNote.java @@ -0,0 +1,37 @@ +package com.mathworks.ci; + +import com.google.common.annotations.VisibleForTesting; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import hudson.Extension; +import hudson.MarkupText; +import hudson.console.ConsoleAnnotationDescriptor; +import hudson.console.ConsoleAnnotator; +import hudson.console.ConsoleNote; +import java.util.regex.Pattern; + + +public class BuildTargetNote extends ConsoleNote { + @VisibleForTesting + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Visible for testing") + public static boolean ENABLED = !Boolean.getBoolean(BuildTargetNote.class.getName() + ".disabled"); + + public BuildTargetNote() { + } + + @Override + public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { + MarkupText.SubText t = text.findToken(Pattern.compile("MATLAB-Build-")); + String taskName = text.subText(13, text.length()-2).getText(); + if (t != null) + t.addMarkup(0, t.length()-1, "", ""); + return null; + } + + @Extension + public static final class DescriptorImpl extends ConsoleAnnotationDescriptor { + public String getDisplayName() { + return "Build targets"; + } + } + +} diff --git a/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java b/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java index 45028232..e57d7b9b 100644 --- a/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java +++ b/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java @@ -5,6 +5,9 @@ * */ +import hudson.util.ArgumentListBuilder; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import javax.annotation.Nonnull; import org.kohsuke.stapler.DataBoundConstructor; @@ -22,7 +25,6 @@ import hudson.model.Computer; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; -import hudson.Util; import jenkins.tasks.SimpleBuildStep; import net.sf.json.JSONObject; @@ -30,6 +32,9 @@ public class RunMatlabBuildBuilder extends Builder implements SimpleBuildStep, M private int buildResult; private String tasks; private StartupOptions startupOptions; + private static String DEFAULT_PLUGIN = "+ciplugins/+jenkins/getDefaultPlugins.m"; + private static String BUILD_REPORT_PLUGIN = "+ciplugins/+jenkins/BuildReportPlugin.m"; + private static String TASK_RUN_PROGRESS_PLUGIN = "+ciplugins/+jenkins/TaskRunProgressPlugin.m"; @DataBoundConstructor public RunMatlabBuildBuilder() {} @@ -93,8 +98,15 @@ public void perform(@Nonnull Run build, @Nonnull FilePath workspace, // Invoke MATLAB build and transfer output to standard // Output Console + buildResult = execMatlabCommand(workspace, launcher, listener, env, build); - buildResult = execMatlabCommand(workspace, launcher, listener, env); + //Add build result action + FilePath jsonFile = new FilePath(workspace, ".matlab/buildArtifact.json"); + if(jsonFile.exists()){ + jsonFile.copyTo(new FilePath(new File(build.getRootDir().getAbsolutePath()+"/buildArtifact.json"))); + jsonFile.delete(); + } + build.addAction(new BuildArtifactAction(build, workspace)); if (buildResult != 0) { build.setResult(Result.FAILURE); @@ -102,7 +114,7 @@ public void perform(@Nonnull Run build, @Nonnull FilePath workspace, } private int execMatlabCommand(FilePath workspace, Launcher launcher, - TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { + TaskListener listener, EnvVars envVars, @Nonnull Run build) throws IOException, InterruptedException { /* * Handle the case for using MATLAB Axis for multi conf projects by adding appropriate @@ -118,11 +130,18 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, // Create MATLAB script createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, listener, envVars); + // Copy JenkinsLogging plugin in temp folder + copyFileInWorkspace(DEFAULT_PLUGIN,DEFAULT_PLUGIN,uniqueTmpFolderPath); + copyFileInWorkspace(BUILD_REPORT_PLUGIN,BUILD_REPORT_PLUGIN,uniqueTmpFolderPath); + copyFileInWorkspace(TASK_RUN_PROGRESS_PLUGIN,TASK_RUN_PROGRESS_PLUGIN,uniqueTmpFolderPath); + ProcStarter matlabLauncher; + BuildConsoleAnnotator bca = new BuildConsoleAnnotator(listener.getLogger(), build.getCharset()); String options = getStartupOptions() == null ? "" : getStartupOptions().getOptions(); try { matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'));"+ uniqueBuildFile, options, uniqueTmpFldrName); + listener.getLogger() .println("#################### Starting command output ####################"); @@ -132,6 +151,8 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, listener.getLogger().println(e.getMessage()); return 1; } finally { + bca.forceEol(); + bca.close(); // Cleanup the tmp directory if (uniqueTmpFolderPath.exists()) { uniqueTmpFolderPath.deleteRecursive(); @@ -145,6 +166,10 @@ private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqu final FilePath matlabCommandFile = new FilePath(uniqueTmpFolderPath, uniqueScriptName + ".m"); final String tasks = envVars.expand(getTasks()); + + // Set ENV variable to override the default plugin list + envVars.put("MW_MATLAB_BUILDTOOL_DEFAULT_PLUGINS_FCN_OVERRIDE", "ciplugins.jenkins.getDefaultPlugins"); + String cmd = "buildtool"; if (!tasks.trim().isEmpty()) { @@ -152,7 +177,7 @@ private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqu } final String matlabCommandFileContent = - "cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; + "addpath(pwd);cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; // Display the commands on console output for users reference listener.getLogger() @@ -160,4 +185,57 @@ private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqu matlabCommandFile.write(matlabCommandFileContent, "UTF-8"); } + + public ProcStarter getProcessToRunMatlabCommand(FilePath workspace, + Launcher launcher, BuildConsoleAnnotator bca, EnvVars envVars, String matlabCommand, String startupOpts, String uniqueName) + throws IOException, InterruptedException { + // Get node specific temp .matlab directory to copy matlab runner script + FilePath targetWorkspace; + ProcStarter matlabLauncher; + ArgumentListBuilder args = new ArgumentListBuilder(); + if (launcher.isUnix()) { + targetWorkspace = new FilePath(launcher.getChannel(), + workspace.getRemote() + "/" + MatlabBuilderConstants.TEMP_MATLAB_FOLDER_NAME); + + // Determine whether we're on Mac on Linux + ByteArrayOutputStream kernelStream = new ByteArrayOutputStream(); + launcher.launch() + .cmds("uname") + .masks(true) + .stdout(kernelStream) + .join(); + + String binaryName; + String runnerName = uniqueName + "/run-matlab-command"; + if (kernelStream.toString("UTF-8").contains("Linux")) { + binaryName = "glnxa64/run-matlab-command"; + } else { + binaryName = "maci64/run-matlab-command"; + } + + args.add(MatlabBuilderConstants.TEMP_MATLAB_FOLDER_NAME + "/" + runnerName); + args.add(matlabCommand); + args.add(startupOpts.split(" ")); + + matlabLauncher = launcher.launch().envs(envVars).cmds(args).stdout(bca); + + // Copy runner for linux platform in workspace. + copyFileInWorkspace(binaryName, runnerName, targetWorkspace); + } else { + targetWorkspace = new FilePath(launcher.getChannel(), + workspace.getRemote() + "\\" + MatlabBuilderConstants.TEMP_MATLAB_FOLDER_NAME); + + final String runnerName = uniqueName + "\\run-matlab-command.exe"; + + args.add(targetWorkspace.toString() + "\\" + runnerName, "\"" + matlabCommand + "\""); + args.add(startupOpts.split(" ")); + + matlabLauncher = launcher.launch().envs(envVars).cmds(args).stdout(bca); + + // Copy runner for Windows platform in workspace. + copyFileInWorkspace("win64/run-matlab-command.exe", runnerName, + targetWorkspace); + } + return matlabLauncher; + } } diff --git a/src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m b/src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m new file mode 100644 index 00000000..5825b614 --- /dev/null +++ b/src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m @@ -0,0 +1,25 @@ +classdef BuildReportPlugin < matlab.buildtool.plugins.BuildRunnerPlugin + +% Copyright 2024 The MathWorks, Inc. + + methods (Access=protected) + + function runTaskGraph(plugin, pluginData) + runTaskGraph@matlab.buildtool.plugins.BuildRunnerPlugin(plugin, pluginData); + fID = fopen('.matlab/buildArtifact.json', 'w'); + taskDetails = struct(); + for idx = 1:numel(pluginData.TaskResults) + taskDetails(idx).name = pluginData.TaskResults(idx).Name; + taskDetails(idx).description = pluginData.TaskGraph.Tasks(idx).Description; + taskDetails(idx).failed = pluginData.TaskResults(idx).Failed; + taskDetails(idx).skipped = pluginData.TaskResults(idx).Skipped; + taskDetails(idx).duration = string(pluginData.TaskResults(idx).Duration); + end + a = struct("taskDetails",taskDetails); + s = jsonencode(a,"PrettyPrint",true); + fprintf(fID, '%s',s); + fclose(fID); + end + + end +end \ No newline at end of file diff --git a/src/main/resources/+ciplugins/+jenkins/TaskRunProgressPlugin.m b/src/main/resources/+ciplugins/+jenkins/TaskRunProgressPlugin.m new file mode 100644 index 00000000..077837ed --- /dev/null +++ b/src/main/resources/+ciplugins/+jenkins/TaskRunProgressPlugin.m @@ -0,0 +1,13 @@ +classdef TaskRunProgressPlugin < matlab.buildtool.plugins.BuildRunnerPlugin +% + +% Copyright 2023 The MathWorks, Inc. + + methods (Access=protected) + + function runTask(plugin, pluginData) + disp("[MATLAB-Build-" + pluginData.TaskResults.Name + "]"); + runTask@matlab.buildtool.plugins.BuildRunnerPlugin(plugin, pluginData); + end + end + end \ No newline at end of file diff --git a/src/main/resources/+ciplugins/+jenkins/getDefaultPlugins.m b/src/main/resources/+ciplugins/+jenkins/getDefaultPlugins.m new file mode 100644 index 00000000..b7a1f839 --- /dev/null +++ b/src/main/resources/+ciplugins/+jenkins/getDefaultPlugins.m @@ -0,0 +1,14 @@ +function plugins = getDefaultPlugins(pluginProviderData) +% + +% Copyright 2024 The MathWorks, Inc. +arguments + pluginProviderData (1,1) struct = struct(); +end + +plugins = [ ... + matlab.buildtool.internal.getFactoryDefaultPlugins(pluginProviderData) ... + ciplugins.jenkins.BuildReportPlugin() ... + ciplugins.jenkins.TaskRunProgressPlugin() ... +]; +end \ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly b/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly new file mode 100644 index 00000000..e8d8ad4b --- /dev/null +++ b/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly @@ -0,0 +1,91 @@ + + + + + + + + + +

+ +

+
+ + + No tasks + + +
+ ${(it.totalCount)} Tasks + + (${h.getDiffString(it.failCount-prev.failCount)}) + + + , Skipped ${(it.skipCount)} + + (${h.getDiffString(it.skipCount-prev.skipCount)}) + + +
+
+ +
+ +
+
+
+ ${(it.failCount)} Failures + + (${h.getDiffString(it.totalCount-prev.totalCount)}) + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Task NameStatus DescriptionDuration (HH:MM:SS)
+ ${p.taskName} + + + + FAILED + + + + PASSED + + + + SKIPPED + + + + + ${p.description} + + ${p.taskDuration}
+
+
+
\ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly new file mode 100644 index 00000000..d98e839f --- /dev/null +++ b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly @@ -0,0 +1,16 @@ + + + +

MATLAB Build Results

+

This build report includes ${it.totalCount} tasks.

+
    +
  1. Number of failed tasks: ${it.failCount}

  2. +
  3. Number of skipped tasks: ${it.skipCount}

  4. +
+
+
\ No newline at end of file diff --git a/src/test/java/com/mathworks/ci/BuildArtifactActionTest.java b/src/test/java/com/mathworks/ci/BuildArtifactActionTest.java new file mode 100644 index 00000000..1bfadb4b --- /dev/null +++ b/src/test/java/com/mathworks/ci/BuildArtifactActionTest.java @@ -0,0 +1,233 @@ +package com.mathworks.ci; + + +import hudson.FilePath; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.json.simple.parser.ParseException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class BuildArtifactActionTest { + private FreeStyleProject project; + private UseMatlabVersionBuildWrapper buildWrapper; + private RunMatlabBuildBuilder scriptBuilder; + + private static String VERSION_INFO_XML_FILE = "VersionInfo.xml"; + + public BuildArtifactActionTest(){ + } + + @Rule + public JenkinsRule jenkins = new JenkinsRule(); + + + @Before + public void testSetup() throws IOException { + this.project = jenkins.createFreeStyleProject(); + this.scriptBuilder = new RunMatlabBuildBuilder(); + this.buildWrapper = new UseMatlabVersionBuildWrapper(); + } + + @After + public void testTearDown() { + this.project = null; + this.scriptBuilder = null; + } + + private String getMatlabroot(String version) throws URISyntaxException { + String defaultVersionInfo = "versioninfo/R2017a/" + VERSION_INFO_XML_FILE; + String userVersionInfo = "versioninfo/" + version + "/" + VERSION_INFO_XML_FILE; + URL matlabRootURL = Optional.ofNullable(getResource(userVersionInfo)) + .orElseGet(() -> getResource(defaultVersionInfo)); + File matlabRoot = new File(matlabRootURL.toURI()); + return matlabRoot.getAbsolutePath().replace(File.separator + VERSION_INFO_XML_FILE, "") + .replace("R2017a", version); + } + + private URL getResource(String resource) { + return BuildArtifactAction.class.getClassLoader().getResource(resource); + } + + /** + * Verify if total BuildArtifacts returned from artifact file. + *5 + */ + + @Test + public void verifyBuildArtifactsReturned() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts/t1/buildArtifact.json","buildArtifact.json",artifactRoot); + List ba = ac.getBuildArtifact(); + int expectedSize = ba.size(); + Assert.assertEquals("The build names are not matching",3,expectedSize); + } + + /** + * Verify if total Failed count returned from artifact file. + * + */ + + @Test + public void verifyFailedCount() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts/t1/buildArtifact.json","buildArtifact.json",artifactRoot); + List ba = ac.getBuildArtifact(); + boolean expectedStatus = ba.get(0).getTaskFailed(); + Assert.assertEquals("The task is passed",false,expectedStatus); + } + + /** + * Verify if total skipped count returned from artifact file. + * + */ + + @Test + public void verifySkipCount() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts.t2/buildArtifact.json","buildArtifact.json",artifactRoot); + List ba = ac.getBuildArtifact(); + Assert.assertEquals("The task is not skipped",true,ba.get(0).getTaskSkipped()); + } + + /** + * Verify if duration returned from artifact file. + * + */ + + @Test + public void verifyDurationIsAccurate() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts.t2/buildArtifact.json","buildArtifact.json",artifactRoot); + List ba = ac.getBuildArtifact(); + Assert.assertEquals("The task duration is not matching","00:02:53",ba.get(0).getTaskDuration()); + } + + /** + * Verify if Task description returned from artifact file. + * + */ + + @Test + public void verifyTaskDescriptionIsAccurate() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts.t2/buildArtifact.json","buildArtifact.json",artifactRoot); + List ba = ac.getBuildArtifact(); + Assert.assertEquals("The task description is not matching","Test show",ba.get(0).getTaskDescription()); + } + + /** + * Verify if Task name returned from artifact file. + * + */ + + @Test + public void verifyTaskNameIsAccurate() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts.t2/buildArtifact.json","buildArtifact.json",artifactRoot); + List ba = ac.getBuildArtifact(); + Assert.assertEquals("The task name is not matching","show",ba.get(0).getTaskName()); + } + + /** + * Verify if total count returned from artifact file. + * + */ + + @Test + public void verifyTotalTaskCountIsAccurate() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts.t2/buildArtifact.json","buildArtifact.json",artifactRoot); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + Assert.assertEquals("Total task count is not correct",1,ac.getTotalCount()); + } + + /** + * Verify if total count returned from artifact file. + * + */ + + @Test + public void verifyTotalTaskCountIsAccurate2() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts/t1/buildArtifact.json","buildArtifact.json",artifactRoot); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + Assert.assertEquals("Total task count is not correct",3,ac.getTotalCount()); + } + + /** + * Verify if total failed count returned from artifact file. + * + */ + + @Test + public void verifyTotalFailedTaskCountIsAccurate() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts/t1/buildArtifact.json","buildArtifact.json",artifactRoot); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + Assert.assertEquals("Total task count is not correct",3,ac.getTotalCount()); + Assert.assertEquals("Total task failed count is not correct",1,ac.getFailCount()); + } + /** + * Verify if total skipped count returned from artifact file. + * + */ + + @Test + public void verifyTotalSkipTaskCountIsAccurate() throws ExecutionException, InterruptedException, URISyntaxException, IOException, ParseException { + FreeStyleBuild build = getFreestyleBuild(); + FilePath artifactRoot = new FilePath(build.getRootDir()); + copyFileInWorkspace("buildArtifacts/t1/buildArtifact.json","buildArtifact.json",artifactRoot); + BuildArtifactAction ac = new BuildArtifactAction(build,build.getWorkspace()); + Assert.assertEquals("Total task count is not correct",3,ac.getTotalCount()); + Assert.assertEquals("Total task skip count is not correct",1,ac.getSkipCount()); + } + + + + private void copyFileInWorkspace(String sourceFile, String targetFile, FilePath targetWorkspace) + throws IOException, InterruptedException { + final ClassLoader classLoader = getClass().getClassLoader(); + FilePath targetFilePath = new FilePath(targetWorkspace, targetFile); + InputStream in = classLoader.getResourceAsStream(sourceFile); + targetFilePath.copyFrom(in); + // set executable permission + targetFilePath.chmod(0777); + } + + private FreeStyleBuild getFreestyleBuild() throws ExecutionException, InterruptedException, URISyntaxException { + this.buildWrapper.setMatlabBuildWrapperContent(new MatlabBuildWrapperContent(Message.getValue("matlab.custom.location"), getMatlabroot("R2017a"))); + project.getBuildWrappersList().add(this.buildWrapper); + scriptBuilder.setTasks(""); + project.getBuildersList().add(this.scriptBuilder); + FreeStyleBuild build = project.scheduleBuild2(0).get(); + return build; + } +} diff --git a/src/test/java/com/mathworks/ci/RunMatlabBuildBuilderTest.java b/src/test/java/com/mathworks/ci/RunMatlabBuildBuilderTest.java index 8d80edf7..ee9b0491 100644 --- a/src/test/java/com/mathworks/ci/RunMatlabBuildBuilderTest.java +++ b/src/test/java/com/mathworks/ci/RunMatlabBuildBuilderTest.java @@ -203,7 +203,8 @@ public void verifyBuildPicksTheCorrectBuildBatch() throws Exception { project.getBuildersList().add(this.scriptBuilder); FreeStyleBuild build = project.scheduleBuild2(0).get(); jenkins.assertLogContains("Generating MATLAB script with content", build); - jenkins.assertLogContains("buildtool compile", build); + jenkins.assertLogContains("buildtool", build); + jenkins.assertLogContains("compile", build); } /* @@ -249,7 +250,7 @@ public void verifyBuildSupportsEnvVar() throws Exception { scriptBuilder.setTasks("$TASKS"); project.getBuildersList().add(scriptBuilder); FreeStyleBuild build = project.scheduleBuild2(0).get(); - jenkins.assertLogContains("buildtool compile", build); + jenkins.assertLogContains("compile", build); } /* diff --git a/src/test/resources/buildArtifacts.t2/buildArtifact.json b/src/test/resources/buildArtifacts.t2/buildArtifact.json new file mode 100644 index 00000000..5c9d061d --- /dev/null +++ b/src/test/resources/buildArtifacts.t2/buildArtifact.json @@ -0,0 +1,9 @@ +{ + "taskDetails": { + "name": "show", + "description": "Test show", + "failed": false, + "skipped": true, + "duration": "00:02:53" + } +} \ No newline at end of file diff --git a/src/test/resources/buildArtifacts/t1/buildArtifact.json b/src/test/resources/buildArtifacts/t1/buildArtifact.json new file mode 100644 index 00000000..f2f5d392 --- /dev/null +++ b/src/test/resources/buildArtifacts/t1/buildArtifact.json @@ -0,0 +1,25 @@ +{ + "taskDetails": [ + { + "name": "check", + "description": "Checks Description", + "failed": false, + "skipped": false, + "duration": "00:00:00" + }, + { + "name": "show", + "description": "", + "failed": true, + "skipped": false, + "duration": "00:00:00" + }, + { + "name": "test", + "description": "tests Dscription", + "failed": false, + "skipped": true, + "duration": "00:00:00" + } + ] +} \ No newline at end of file From 6265ee071c9eed07a16ca84cb52a29863a6aecf9 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:04:10 +0530 Subject: [PATCH 14/20] Build visulization support for pipeline (#278) --- .../ci/MatlabBuildStepExecution.java | 94 +++++++++++++++++-- .../mathworks/ci/RunMatlabBuildBuilder.java | 6 +- .../ci/BuildArtifactAction/index.jelly | 10 +- 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java b/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java index 6f554c29..7f587321 100644 --- a/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java +++ b/src/main/java/com/mathworks/ci/MatlabBuildStepExecution.java @@ -5,6 +5,10 @@ * */ +import hudson.model.Run; +import hudson.util.ArgumentListBuilder; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import org.jenkinsci.plugins.workflow.steps.StepContext; import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; @@ -21,6 +25,9 @@ public class MatlabBuildStepExecution extends SynchronousNonBlockingStepExecutio private String tasks; private String startupOptions; + private static String DEFAULT_PLUGIN = "+ciplugins/+jenkins/getDefaultPlugins.m"; + private static String BUILD_REPORT_PLUGIN = "+ciplugins/+jenkins/BuildReportPlugin.m"; + private static String TASK_RUN_PROGRESS_PLUGIN = "+ciplugins/+jenkins/TaskRunProgressPlugin.m"; public MatlabBuildStepExecution(StepContext context, String tasks, String startupOptions) { super(context); @@ -38,12 +45,21 @@ public Void run() throws Exception { final FilePath workspace = getContext().get(FilePath.class); final TaskListener listener = getContext().get(TaskListener.class); final EnvVars env = getContext().get(EnvVars.class); + final Run build = getContext().get(Run.class); //Make sure the Workspace exists before run workspace.mkdirs(); + System.out.println("THE ROOT DIR IS"+ build.getRootDir().toString()); - int exitCode = execMatlabCommand(workspace, launcher, listener, env); + int exitCode = execMatlabCommand(workspace, launcher, listener, env, build); + //Add build result action + FilePath jsonFile = new FilePath(workspace, ".matlab/buildArtifact.json"); + if(jsonFile.exists()){ + jsonFile.copyTo(new FilePath(new File(build.getRootDir().getAbsolutePath()+"/buildArtifact.json"))); + jsonFile.delete(); + } + build.addAction(new BuildArtifactAction(build, workspace)); if(exitCode != 0){ // throw an exception if return code is non-zero @@ -60,7 +76,7 @@ public void stop(Throwable cause) throws Exception { } private int execMatlabCommand(FilePath workspace, Launcher launcher, - TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { + TaskListener listener, EnvVars envVars, Run build) throws IOException, InterruptedException { final String uniqueTmpFldrName = getUniqueNameForRunnerFile(); final String uniqueBuildFile = "build_" + getUniqueNameForRunnerFile().replaceAll("-", "_"); @@ -68,12 +84,18 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace); // Create MATLAB script - createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, listener); + createMatlabScriptByName(uniqueTmpFolderPath, uniqueBuildFile, listener, envVars); + + // Copy JenkinsLogging plugin in temp folder + copyFileInWorkspace(DEFAULT_PLUGIN,DEFAULT_PLUGIN,uniqueTmpFolderPath); + copyFileInWorkspace(BUILD_REPORT_PLUGIN,BUILD_REPORT_PLUGIN,uniqueTmpFolderPath); + copyFileInWorkspace(TASK_RUN_PROGRESS_PLUGIN,TASK_RUN_PROGRESS_PLUGIN,uniqueTmpFolderPath); + ProcStarter matlabLauncher; - try { - matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, - "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"')); "+ uniqueBuildFile, startupOptions, uniqueTmpFldrName); + try (BuildConsoleAnnotator bca = new BuildConsoleAnnotator(listener.getLogger(), build.getCharset())) { + matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, bca, envVars, + "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'));"+ uniqueBuildFile, startupOptions, uniqueTmpFldrName); listener.getLogger() .println("#################### Starting command output ####################"); return matlabLauncher.pwd(workspace).join(); @@ -86,19 +108,22 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, } } - private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, TaskListener listener) throws IOException, InterruptedException { + private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqueScriptName, TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { // Create a new command runner script in the temp folder. final FilePath matlabBuildFile = new FilePath(uniqueTmpFolderPath, uniqueScriptName + ".m"); final String tasks = getContext().get(EnvVars.class).expand(getTasks()); + + // Set ENV variable to override the default plugin list + envVars.put("MW_MATLAB_BUILDTOOL_DEFAULT_PLUGINS_FCN_OVERRIDE", "ciplugins.jenkins.getDefaultPlugins"); String cmd = "buildtool"; if (!tasks.trim().isEmpty()) { cmd += " " + tasks; } final String matlabBuildFileContent = - "cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; + "addpath(pwd);cd(getenv('MW_ORIG_WORKING_FOLDER'));\n" + cmd; // Display the commands on console output for users reference listener.getLogger() @@ -106,4 +131,57 @@ private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqu matlabBuildFile.write(matlabBuildFileContent, "UTF-8"); } + + public ProcStarter getProcessToRunMatlabCommand(FilePath workspace, + Launcher launcher, BuildConsoleAnnotator bca, EnvVars envVars, String matlabCommand, String startupOpts, String uniqueName) + throws IOException, InterruptedException { + // Get node specific temp .matlab directory to copy matlab runner script + FilePath targetWorkspace; + ProcStarter matlabLauncher; + ArgumentListBuilder args = new ArgumentListBuilder(); + if (launcher.isUnix()) { + targetWorkspace = new FilePath(launcher.getChannel(), + workspace.getRemote() + "/" + MatlabBuilderConstants.TEMP_MATLAB_FOLDER_NAME); + + // Determine whether we're on Mac on Linux + ByteArrayOutputStream kernelStream = new ByteArrayOutputStream(); + launcher.launch() + .cmds("uname") + .masks(true) + .stdout(kernelStream) + .join(); + + String binaryName; + String runnerName = uniqueName + "/run-matlab-command"; + if (kernelStream.toString("UTF-8").contains("Linux")) { + binaryName = "glnxa64/run-matlab-command"; + } else { + binaryName = "maci64/run-matlab-command"; + } + + args.add(MatlabBuilderConstants.TEMP_MATLAB_FOLDER_NAME + "/" + runnerName); + args.add(matlabCommand); + args.add(startupOpts.split(" ")); + + matlabLauncher = launcher.launch().envs(envVars).cmds(args).stdout(bca); + + // Copy runner for linux platform in workspace. + copyFileInWorkspace(binaryName, runnerName, targetWorkspace); + } else { + targetWorkspace = new FilePath(launcher.getChannel(), + workspace.getRemote() + "\\" + MatlabBuilderConstants.TEMP_MATLAB_FOLDER_NAME); + + final String runnerName = uniqueName + "\\run-matlab-command.exe"; + + args.add(targetWorkspace.toString() + "\\" + runnerName, "\"" + matlabCommand + "\""); + args.add(startupOpts.split(" ")); + + matlabLauncher = launcher.launch().envs(envVars).cmds(args).stdout(bca); + + // Copy runner for Windows platform in workspace. + copyFileInWorkspace("win64/run-matlab-command.exe", runnerName, + targetWorkspace); + } + return matlabLauncher; + } } diff --git a/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java b/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java index e57d7b9b..58bb773d 100644 --- a/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java +++ b/src/main/java/com/mathworks/ci/RunMatlabBuildBuilder.java @@ -139,7 +139,7 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, BuildConsoleAnnotator bca = new BuildConsoleAnnotator(listener.getLogger(), build.getCharset()); String options = getStartupOptions() == null ? "" : getStartupOptions().getOptions(); try { - matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, + matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, bca, envVars, "setenv('MW_ORIG_WORKING_FOLDER', cd('"+ uniqueTmpFolderPath.getRemote().replaceAll("'", "''") +"'));"+ uniqueBuildFile, options, uniqueTmpFldrName); @@ -152,7 +152,6 @@ private int execMatlabCommand(FilePath workspace, Launcher launcher, return 1; } finally { bca.forceEol(); - bca.close(); // Cleanup the tmp directory if (uniqueTmpFolderPath.exists()) { uniqueTmpFolderPath.deleteRecursive(); @@ -186,7 +185,8 @@ private void createMatlabScriptByName(FilePath uniqueTmpFolderPath, String uniqu matlabCommandFile.write(matlabCommandFileContent, "UTF-8"); } - public ProcStarter getProcessToRunMatlabCommand(FilePath workspace, + + private ProcStarter getProcessToRunMatlabCommand(FilePath workspace, Launcher launcher, BuildConsoleAnnotator bca, EnvVars envVars, String matlabCommand, String startupOpts, String uniqueName) throws IOException, InterruptedException { // Get node specific temp .matlab directory to copy matlab runner script diff --git a/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly b/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly index e8d8ad4b..9942cdfc 100644 --- a/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly +++ b/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly @@ -59,14 +59,14 @@ - ${p.taskName} + ${p.taskName} - - FAILED + + FAILED - + PASSED @@ -78,7 +78,7 @@ - ${p.description} + ${p.taskDescription} ${p.taskDuration} From f2908d05bb52949e13bdca3d684aa0c0d39a4e4d Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Wed, 31 Jan 2024 13:23:33 +0530 Subject: [PATCH 15/20] Fixed issue 281 (#284) --- src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m b/src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m index 5825b614..ccdfbd94 100644 --- a/src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m +++ b/src/main/resources/+ciplugins/+jenkins/BuildReportPlugin.m @@ -6,7 +6,7 @@ function runTaskGraph(plugin, pluginData) runTaskGraph@matlab.buildtool.plugins.BuildRunnerPlugin(plugin, pluginData); - fID = fopen('.matlab/buildArtifact.json', 'w'); + fID = fopen(fullfile(getenv("WORKSPACE"),'.matlab/buildArtifact.json'), 'w'); taskDetails = struct(); for idx = 1:numel(pluginData.TaskResults) taskDetails(idx).name = pluginData.TaskResults(idx).Name; From c04b1dd77ab650e46c7f8a832d1cf124306c5b18 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:37:49 +0530 Subject: [PATCH 16/20] Update the maven release (#286) * [maven-release-plugin] prepare release matlab-2.12.0 * [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dfd69040..83868f62 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ matlab - 2.11.2-SNAPSHOT + 2.12.1-SNAPSHOT hpi From 41a0fa81726f5a8bc3104bba092b634685fcddff Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:13:17 +0530 Subject: [PATCH 17/20] Added summary page warnning --- .../com/mathworks/ci/BuildArtifactAction/summary.jelly | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly index d98e839f..26113f2e 100644 --- a/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly +++ b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly @@ -7,6 +7,11 @@ xmlns:i="jelly:fmt">

MATLAB Build Results

+ + +
Zero task indicates either none of the task ran from buildfile or MATLAB version does not support the feature
+
+

This build report includes ${it.totalCount} tasks.

  1. Number of failed tasks: ${it.failCount}

  2. From 353a35f0f56edf1bdf8bfcf6f46f72327bdd9984 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:00:17 +0530 Subject: [PATCH 18/20] Updated summary warnning --- .../com/mathworks/ci/BuildArtifactAction/summary.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly index 26113f2e..22549082 100644 --- a/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly +++ b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly @@ -9,7 +9,7 @@

    MATLAB Build Results

    -
    Zero task indicates either none of the task ran from buildfile or MATLAB version does not support the feature
    +
    Make sure that you are using MATLAB R2022b or later and that your build file contains no errors.

    This build report includes ${it.totalCount} tasks.

    From 6e3869c18740dfc8ba116cb239de78f8f813b4c2 Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:31:59 +0530 Subject: [PATCH 19/20] Fixed Summary page issues --- .../mathworks/ci/BuildArtifactAction/summary.jelly | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly index 22549082..20599d5a 100644 --- a/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly +++ b/src/main/resources/com/mathworks/ci/BuildArtifactAction/summary.jelly @@ -6,16 +6,11 @@ xmlns:f="/lib/form" xmlns:i="jelly:fmt"> -

    MATLAB Build Results

    - - -
    Make sure that you are using MATLAB R2022b or later and that your build file contains no errors.
    -
    -
    -

    This build report includes ${it.totalCount} tasks.

    -
      +

      MATLAB Build Result

      +

      Total task ran: ${it.totalCount}

      +
      • Number of failed tasks: ${it.failCount}

      • Number of skipped tasks: ${it.skipCount}

      • -
    +
    \ No newline at end of file From f70394db46aecede4d5d91f5a0ce5ce2551fe97b Mon Sep 17 00:00:00 2001 From: Nikhil Bhoski <47204011+nbhoski@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:08:45 +0530 Subject: [PATCH 20/20] Fixed issue 292 --- src/main/java/com/mathworks/ci/BuildTargetNote.java | 1 + .../com/mathworks/ci/BuildArtifactAction/index.jelly | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mathworks/ci/BuildTargetNote.java b/src/main/java/com/mathworks/ci/BuildTargetNote.java index 0367df4a..51ec6137 100644 --- a/src/main/java/com/mathworks/ci/BuildTargetNote.java +++ b/src/main/java/com/mathworks/ci/BuildTargetNote.java @@ -22,6 +22,7 @@ public BuildTargetNote() { public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { MarkupText.SubText t = text.findToken(Pattern.compile("MATLAB-Build-")); String taskName = text.subText(13, text.length()-2).getText(); + taskName = taskName.replace("]","").trim(); if (t != null) t.addMarkup(0, t.length()-1, "", ""); return null; diff --git a/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly b/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly index 9942cdfc..c8cf845c 100644 --- a/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly +++ b/src/main/resources/com/mathworks/ci/BuildArtifactAction/index.jelly @@ -59,12 +59,12 @@ - ${p.taskName} + ${p.taskName} - FAILED + FAILED