diff --git a/pom.xml b/pom.xml index 1e9b959f..b78aa77e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.jenkins-ci.plugins plugin - 3.4 + 3.57 matlab @@ -20,7 +20,7 @@ - 2.7.3 + 2.164.3 8 @@ -52,14 +52,57 @@ http://github.com/jenkinsci/matlab-plugin HEAD - + + + + io.jenkins.tools.bom + bom-2.164.x + 4 + import + pom + + + - - - org.jenkins-ci.plugins - matrix-project - 1.14 - + + + + + org.jenkins-ci.plugins + matrix-project + + + + org.jenkins-ci.plugins.workflow + workflow-step-api + + + org.jenkins-ci.plugins.workflow + workflow-api + + + + + + org.jenkins-ci.plugins.workflow + workflow-basic-steps + test + + + org.jenkins-ci.plugins.workflow + workflow-cps + test + + + org.jenkins-ci.plugins.workflow + workflow-durable-task-step + test + + + org.jenkins-ci.plugins.workflow + workflow-job + test + diff --git a/src/main/java/com/mathworks/ci/MatlabCommandStepExecution.java b/src/main/java/com/mathworks/ci/MatlabCommandStepExecution.java new file mode 100644 index 00000000..892d1fa2 --- /dev/null +++ b/src/main/java/com/mathworks/ci/MatlabCommandStepExecution.java @@ -0,0 +1,106 @@ +package com.mathworks.ci; + +import java.io.IOException; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; +import hudson.Launcher.ProcStarter; +import hudson.model.Result; +import hudson.model.TaskListener; + +public class MatlabCommandStepExecution extends StepExecution implements MatlabBuild { + + private static final long serialVersionUID = 1957239693658914450L; + + private String command; + private EnvVars env; + + + public MatlabCommandStepExecution(StepContext context, String command) { + super(context); + this.command = command; + } + + private String getCommand() { + return this.env == null ? this.command : this.env.expand(this.command ); + } + + private void setEnv(EnvVars env) { + this.env = env; + } + + @Override + public boolean start() throws Exception { + final Launcher launcher = getContext().get(Launcher.class); + final FilePath workspace = getContext().get(FilePath.class); + final TaskListener listener = getContext().get(TaskListener.class); + final EnvVars env = getContext().get(EnvVars.class); + setEnv(env); + + //Make sure the Workspace exists before run + + workspace.mkdirs(); + + int res = execMatlabCommand(workspace, launcher, listener, env); + + getContext().setResult((res == 0) ? Result.SUCCESS : Result.FAILURE); + + getContext().onSuccess(true); + + //return false represents the asynchronous run. + return false; + } + + @Override + public void stop(Throwable cause) throws Exception { + getContext().onFailure(cause); + } + + private synchronized int execMatlabCommand(FilePath workspace, Launcher launcher, + TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { + final String uniqueTmpFldrName = getUniqueNameForRunnerFile(); + final String uniqueCommandFile = + "command_" + getUniqueNameForRunnerFile().replaceAll("-", "_"); + final FilePath uniqeTmpFolderPath = + getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace); + + // Create MATLAB script + createMatlabScriptByName(uniqeTmpFolderPath, uniqueCommandFile, workspace, listener); + ProcStarter matlabLauncher; + + try { + matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, + uniqueCommandFile, uniqueTmpFldrName); + launcher.launch().pwd(uniqeTmpFolderPath).envs(envVars); + listener.getLogger() + .println("#################### Starting command output ####################"); + return matlabLauncher.pwd(uniqeTmpFolderPath).join(); + + } catch (Exception e) { + listener.getLogger().println(e.getMessage()); + return 1; + } finally { + // Cleanup the tmp directory + if (uniqeTmpFolderPath.exists()) { + uniqeTmpFolderPath.deleteRecursive(); + } + } + } + + private void createMatlabScriptByName(FilePath uniqeTmpFolderPath, String uniqueScriptName, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { + + // Create a new command runner script in the temp folder. + final FilePath matlabCommandFile = + new FilePath(uniqeTmpFolderPath, uniqueScriptName + ".m"); + final String matlabCommandFileContent = + "cd '" + workspace.getRemote().replaceAll("'", "''") + "';\n" + getCommand(); + + // Display the commands on console output for users reference + listener.getLogger() + .println("Generating MATLAB script with content:\n" + getCommand() + "\n"); + + matlabCommandFile.write(matlabCommandFileContent, "UTF-8"); + } +} \ No newline at end of file diff --git a/src/main/java/com/mathworks/ci/MatlabRunTestsStepExecution.java b/src/main/java/com/mathworks/ci/MatlabRunTestsStepExecution.java new file mode 100644 index 00000000..d5fc90b3 --- /dev/null +++ b/src/main/java/com/mathworks/ci/MatlabRunTestsStepExecution.java @@ -0,0 +1,82 @@ +package com.mathworks.ci; + +/** + * Copyright 2020 The MathWorks, Inc. + * + */ + +import java.io.IOException; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; +import hudson.Launcher.ProcStarter; +import hudson.model.Result; +import hudson.model.TaskListener; + +public class MatlabRunTestsStepExecution extends StepExecution implements MatlabBuild { + + private static final long serialVersionUID = 6704588180717665100L; + + private String command; + + + public MatlabRunTestsStepExecution(StepContext context, String command) { + super(context); + this.command = command; + } + + private String getCommand() { + return this.command; + } + + @Override + public boolean start() throws Exception { + final Launcher launcher = getContext().get(Launcher.class); + final FilePath workspace = getContext().get(FilePath.class); + final TaskListener listener = getContext().get(TaskListener.class); + final EnvVars env = getContext().get(EnvVars.class); + + //Make sure the Workspace exists before run + + workspace.mkdirs(); + + int res = execMatlabCommand(workspace, launcher, listener, env); + + getContext().setResult((res == 0) ? Result.SUCCESS : Result.FAILURE); + + getContext().onSuccess(true); + + //return false represents the asynchronous run. + return false; + } + + @Override + public void stop(Throwable cause) throws Exception { + getContext().onFailure(cause); + } + + private synchronized int execMatlabCommand(FilePath workspace, Launcher launcher, + TaskListener listener, EnvVars envVars) throws IOException, InterruptedException { + final String uniqueTmpFldrName = getUniqueNameForRunnerFile(); + try { + ProcStarter matlabLauncher = getProcessToRunMatlabCommand(workspace, launcher, listener, envVars, + envVars.expand(getCommand()), uniqueTmpFldrName); + + + return matlabLauncher.pwd(workspace).join(); + } catch (Exception e) { + listener.getLogger().println(e.getMessage()); + return 1; + } finally { + // Cleanup the runner File from tmp directory + final FilePath matlabRunnerScript = + getFilePathForUniqueFolder(launcher, uniqueTmpFldrName, workspace); + if (matlabRunnerScript.exists()) { + matlabRunnerScript.deleteRecursive(); + } + } + + } +} diff --git a/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java b/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java index 69bc619b..e1921513 100644 --- a/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java +++ b/src/main/java/com/mathworks/ci/RunMatlabCommandBuilder.java @@ -61,7 +61,7 @@ private EnvVars getEnv() { return this.env; } - @Symbol("RunMatlabCommand") + @Extension public static class RunMatlabCommandDescriptor extends BuildStepDescriptor { diff --git a/src/main/java/com/mathworks/ci/RunMatlabCommandStep.java b/src/main/java/com/mathworks/ci/RunMatlabCommandStep.java new file mode 100644 index 00000000..2449c0c9 --- /dev/null +++ b/src/main/java/com/mathworks/ci/RunMatlabCommandStep.java @@ -0,0 +1,63 @@ +package com.mathworks.ci; + +/** + * Copyright 2020 The MathWorks, Inc. + * + */ + +import java.util.Set; +import org.jenkinsci.plugins.workflow.steps.Step; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepDescriptor; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.DataBoundConstructor; +import com.google.common.collect.ImmutableSet; +import hudson.EnvVars; +import hudson.Extension; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Run; +import hudson.model.TaskListener; + +public class RunMatlabCommandStep extends Step { + + + private String command; + + @DataBoundConstructor + public RunMatlabCommandStep(String command) { + this.command = command; + } + + + public String getCommand() { + return this.command; + } + + @Override + public StepExecution start(StepContext context) throws Exception { + return new MatlabCommandStepExecution(context, getCommand()); + } + + @Extension + public static class CommandStepDescriptor extends StepDescriptor { + + @Override + public Set> getRequiredContext() { + return ImmutableSet.of(TaskListener.class, FilePath.class, Launcher.class, + EnvVars.class, Run.class); + } + + @Override + public String getFunctionName() { + return Message.getValue("matlab.command.build.step.name"); + } + + @Override + public String getDisplayName() { + return Message.getValue("matlab.command.step.display.name"); + } + } +} + + diff --git a/src/main/java/com/mathworks/ci/RunMatlabTestsBuilder.java b/src/main/java/com/mathworks/ci/RunMatlabTestsBuilder.java index 800cd77b..c69985a7 100644 --- a/src/main/java/com/mathworks/ci/RunMatlabTestsBuilder.java +++ b/src/main/java/com/mathworks/ci/RunMatlabTestsBuilder.java @@ -185,7 +185,7 @@ protected Object readResolve() { } - @Symbol("RunMatlabTests") + @Extension public static class RunMatlabTestsDescriptor extends BuildStepDescriptor { diff --git a/src/main/java/com/mathworks/ci/RunMatlabTestsStep.java b/src/main/java/com/mathworks/ci/RunMatlabTestsStep.java new file mode 100644 index 00000000..d365fbd0 --- /dev/null +++ b/src/main/java/com/mathworks/ci/RunMatlabTestsStep.java @@ -0,0 +1,185 @@ +package com.mathworks.ci; +/** + * Copyright 2020 The MathWorks, Inc. + * + */ +import java.io.IOException; +import java.io.InputStream; + +/** + * Copyright 2020 The MathWorks, Inc. + * + */ + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.io.FilenameUtils; +import org.jenkinsci.plugins.workflow.steps.Step; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepDescriptor; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import com.google.common.collect.ImmutableSet; +import hudson.EnvVars; +import hudson.Extension; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Run; +import hudson.model.TaskListener; + +public class RunMatlabTestsStep extends Step { + + private String testResultsPDF; + private String testResultsTAP; + private String testResultsJUnit; + private String codeCoverageCobertura; + private String testResultsSimulinkTest; + private String modelCoverageCobertura; + + + @DataBoundConstructor + public RunMatlabTestsStep() { + + } + + public String getTestResultsTAP() { + return testResultsTAP; + } + + @DataBoundSetter + public void setTestResultsTAP(String testResultsTAP) { + this.testResultsTAP = testResultsTAP; + } + + public String getTestResultsPDF() { + return testResultsPDF; + } + + @DataBoundSetter + public void setTestResultsPDF(String testResultsPDF) { + this.testResultsPDF = testResultsPDF; + } + + public String getTestResultsJUnit() { + return testResultsJUnit; + } + + @DataBoundSetter + public void setTestResultsJUnit(String testResultsJUnit) { + this.testResultsJUnit = testResultsJUnit; + } + + public String getCodeCoverageCobertura() { + return codeCoverageCobertura; + } + + @DataBoundSetter + public void setCodeCoverageCobertura(String codeCoverageCobertura) { + this.codeCoverageCobertura = codeCoverageCobertura; + } + + public String getTestResultsSimulinkTest() { + return testResultsSimulinkTest; + } + + @DataBoundSetter + public void setTestResultsSimulinkTest(String testResultsSimulinkTest) { + this.testResultsSimulinkTest = testResultsSimulinkTest; + } + + public String getModelCoverageCobertura() { + return modelCoverageCobertura; + } + + + @DataBoundSetter + public void setModelCoverageCobertura(String modelCoverageCobertura) { + this.modelCoverageCobertura = modelCoverageCobertura; + } + + + @Override + public StepExecution start(StepContext context) throws Exception { + Launcher launcher = context.get(Launcher.class); + FilePath workspace = context.get(FilePath.class); + + //Copy Scratch file needed to run MATLAB tests in workspace + FilePath targetWorkspace = new FilePath(launcher.getChannel(), workspace.getRemote()); + copyScratchFileInWorkspace(MatlabBuilderConstants.MATLAB_TESTS_RUNNER_RESOURCE, + MatlabBuilderConstants.MATLAB_TESTS_RUNNER_TARGET_FILE, targetWorkspace); + return new MatlabRunTestsStepExecution(context,constructCommandForTest(getInputArgs())); + } + + @Extension + public static class RunTestsStepDescriptor extends StepDescriptor { + + @Override + public Set> getRequiredContext() { + return ImmutableSet.of(TaskListener.class, FilePath.class, Launcher.class, + EnvVars.class, Run.class); + } + + @Override + public String getFunctionName() { + return Message.getValue("matlab.tests.build.step.name"); + } + + @Override + public String getDisplayName() { + return Message.getValue("matlab.tests.step.display.name"); + } + } + + public String constructCommandForTest(String inputArguments) { + final String matlabFunctionName = + FilenameUtils.removeExtension(MatlabBuilderConstants.MATLAB_TESTS_RUNNER_TARGET_FILE); + final String runCommand = "exit(" + matlabFunctionName + "(" + inputArguments + "))"; + return runCommand; + } + + + private String getInputArgs() { + final List inputArgs = new ArrayList<>(); + final Map args = getMatlabArgs(); + + args.forEach((key, val) -> { + if (val != null) { + inputArgs.add("'" + key + "'" + "," + "'" + val.replaceAll("'", "''") + "'"); + } + }); + + if (inputArgs.isEmpty()) { + return ""; + } + + return String.join(",", inputArgs); + } + + private Map getMatlabArgs() { + final Map args = new HashMap(); + args.put("PDFReportPath", getTestResultsPDF()); + args.put("TAPResultsPath", getTestResultsTAP()); + args.put("JUnitResultsPath", getTestResultsJUnit()); + args.put("SimulinkTestResultsPath", getTestResultsSimulinkTest()); + args.put("CoberturaCodeCoveragePath", getCodeCoverageCobertura()); + args.put("CoberturaModelCoveragePath", getModelCoverageCobertura()); + return args; + } + + /* + * Method to copy given file from source to target node specific workspace. + */ + private void copyScratchFileInWorkspace(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(0755); + } +} diff --git a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java index cf6a2eb0..b5a2702d 100644 --- a/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java +++ b/src/main/java/com/mathworks/ci/UseMatlabVersionBuildWrapper.java @@ -56,7 +56,7 @@ private void setEnv(EnvVars env) { this.env = env; } - @Symbol("Matlab") + @Extension public static final class UseMatlabVersionDescriptor extends BuildWrapperDescriptor { diff --git a/src/main/resources/com/mathworks/ci/RunMatlabCommandStep/config.jelly b/src/main/resources/com/mathworks/ci/RunMatlabCommandStep/config.jelly new file mode 100644 index 00000000..28414b78 --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabCommandStep/config.jelly @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabCommandStep/help-command.html b/src/main/resources/com/mathworks/ci/RunMatlabCommandStep/help-command.html new file mode 100644 index 00000000..34f7c8ac --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabCommandStep/help-command.html @@ -0,0 +1,10 @@ +
+ Insert the MATLAB commands you want to execute in the Command box. If you need to specify more than one command, use a comma or semicolon to separate the commands.
+ Example: results = runtests, assertSuccess(results);

+ If you need to specify several MATLAB commands, consider writing a MATLAB script or function and executing this script or function instead. (To run a script or function, do not specify the file extension.)
+ Example: myscript
+
 
+ Note:
  • The build fails if the execution of any command results in an error.
  • +
  • If the build uses a MATLAB version prior to R2020a, MATLAB might display non-ASCII characters, specified in the Command box, as garbled text. If you specify non-ASCII characters in your commands, consider running your commands as a .m or .mlx file created in the same locale that MATLAB uses on the build agent
  • +
+
\ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/config.jelly b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/config.jelly new file mode 100644 index 00000000..564268fd --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/config.jelly @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-codeCoverageCobertura.html b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-codeCoverageCobertura.html new file mode 100644 index 00000000..878f9922 --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-codeCoverageCobertura.html @@ -0,0 +1,6 @@ +
+
+Generate a code coverage report in Cobertura XML format. By default, MATLAB names the artifact cobertura.xml and stores it in the matlabTestArtifacts folder of the project workspace. +To override the default artifact name and location, specify a path relative to the project folder in the File path box. If the text box is empty, MATLAB does not generate an artifact. +
+
\ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-modelCoverageCobertura.html b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-modelCoverageCobertura.html new file mode 100644 index 00000000..4587cd6a --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-modelCoverageCobertura.html @@ -0,0 +1,8 @@ +
+
+Generate a model coverage report in Cobertura XML format. This artifact includes coverage results for Simulink® models that are tested using Simulink Test™. +By default, MATLAB names the artifact coberturamodelcoverage.xml and stores it in the matlabTestArtifacts folder of the project workspace. +
+
Note: This feature requires a Simulink Coverage™ license and is supported only in MATLAB R2018b or a newer release.
+
+
\ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsJUnit.html b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsJUnit.html new file mode 100644 index 00000000..dcb3b8a3 --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsJUnit.html @@ -0,0 +1,6 @@ +
+
+Generate a test report in JUnit XML format. By default, MATLAB names the artifact junittestresults.xml and stores it in the matlabTestArtifacts folder of the project workspace. +To override the default artifact name and location, specify a path relative to the project folder in the File path box. If the text box is empty, MATLAB does not generate an artifact. +
+
\ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsPDF.html b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsPDF.html new file mode 100644 index 00000000..c6e13ddf --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsPDF.html @@ -0,0 +1,11 @@ +
+
+Generate a test report in PDF format. By default, MATLAB names the artifact testreport.pdf and stores it in the matlabTestArtifacts folder of the project workspace. +To override the default artifact name and location, specify a path relative to the project folder in the File path box. If the text box is empty, MATLAB does not generate an artifact. +

+Due to Jenkins Content Security Policy rules, the generated report might not open properly from within the Jenkins workspace. +Consider copying the report to a location outside the workspace and opening it from there. For more information, +see Configuring Jenkins Content Security Policy. +

+Note: This feature is not currently supported on Mac platforms. +
\ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsSimulinkTest.html b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsSimulinkTest.html new file mode 100644 index 00000000..4c50726a --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsSimulinkTest.html @@ -0,0 +1,7 @@ +
+
Export Simulink Test™ Manager results in MLDATX format. By default, MATLAB names the artifact simulinktestresults.mldatx and stores it in the matlabTestArtifacts folder of the project workspace. +To override the default artifact name and location, specify a path relative to the project folder in the File path box. If the text box is empty, MATLAB does not generate an artifact. +
+
+Note: This feature requires a Simulink Test license and is supported only in MATLAB R2019a or a newer release. +
\ No newline at end of file diff --git a/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsTAP.html b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsTAP.html new file mode 100644 index 00000000..4f22f96a --- /dev/null +++ b/src/main/resources/com/mathworks/ci/RunMatlabTestsStep/help-testResultsTAP.html @@ -0,0 +1,6 @@ +
+
+Generate a test report in TAP format. By default, MATLAB names the artifact taptestresults.tap and stores it in the matlabTestArtifacts folder of the project workspace. +To override the default artifact name and location, specify a path relative to the project folder in the File path box. If the text box is empty, MATLAB does not generate an artifact. +
+
\ No newline at end of file diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index 7cf08e48..ce5589d0 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -16,4 +16,8 @@ Builder.matlab.modelcoverage.support.warning = To generate a Cobertura model cov Builder.matlab.exportstmresults.support.warning = To export Simulink Test Manager results, use MATLAB R2019a or a newer release. Builder.matlab.runner.script.target.file.linux.name = run_matlab_command.sh Builder.matlab.runner.script.target.file.windows.name = run_matlab_command.bat -build.workspace.computer.not.found = Unable to access the computer for this build. \ No newline at end of file +build.workspace.computer.not.found = Unable to access the computer for this build. +matlab.command.build.step.name = runMATLABCommand +matlab.tests.build.step.name = runMATLABTests +matlab.command.step.display.name = Run MATLAB commands, scripts, or functions +matlab.tests.step.display.name = Run MATLAB tests and generate artifacts \ No newline at end of file diff --git a/src/test/java/com/mathworks/ci/RunMatlabCommandStepTest.java b/src/test/java/com/mathworks/ci/RunMatlabCommandStepTest.java new file mode 100644 index 00000000..36fe579c --- /dev/null +++ b/src/test/java/com/mathworks/ci/RunMatlabCommandStepTest.java @@ -0,0 +1,107 @@ +package com.mathworks.ci; +/** + * Copyright 2020 The MathWorks, Inc. + * + */ + +import java.io.IOException; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import hudson.FilePath; +import hudson.slaves.DumbSlave; + +public class RunMatlabCommandStepTest { + + + private WorkflowJob project; + + + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Before + public void testSetup() throws IOException { + this.project = j.createProject(WorkflowJob.class); + } + + + /* + * Verify when MATLAB is not in PATH variable. + */ + + @Test + public void verifyMATLABPathNotSet() throws Exception { + project.setDefinition( + new CpsFlowDefinition("node { runMATLABCommand(command: 'pwd')}", true)); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("MATLAB_ROOT", build); + } + + /* + * Verify MATLAB is invoked when valid MATLAB is in PATH. + * + */ + + @Test + public void verifyMATLABPathSet() throws Exception { + project.setDefinition( + new CpsFlowDefinition("node { testMATLABCommand(command: 'pwd')}", true)); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("tester_started", build); + } + + /* + * Verify Pipeline script runs on Slave with valid MATLAB + * + */ + + @Test + public void verifyPipelineOnSlave() throws Exception { + DumbSlave s = j.createOnlineSlave(); + project.setDefinition(new CpsFlowDefinition( + "node('!master') { testMATLABCommand(command: 'pwd')}", + true)); + + s.getWorkspaceFor(project); + WorkflowRun build = project.scheduleBuild2(0).get(); + + j.assertBuildStatusSuccess(build); + } + + /* + * Verify appropriate command is invoked as in pipeline script + * + */ + + @Test + public void verifyCommandSameAsScript() throws Exception { + project.setDefinition( + new CpsFlowDefinition("node { runMATLABCommand(command: 'pwd')}", true)); + + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("pwd", build); + } + + /* + * Verify script can run Matrix build + * + */ + + @Test + public void verifyMatrixBuild() throws Exception { + project.setDefinition(new CpsFlowDefinition( + "node { matrix {\n" + "agent any\n" + "axes {\n" + "axis {\n" + "name: 'CMD'\n" + + "values: 'pwd','ver'\n }}\n" + "runMATLABCommand(command: '${CMD}')}}", + true)); + + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("pwd", build); + j.assertLogContains("ver", build); + } +} diff --git a/src/test/java/com/mathworks/ci/RunMatlabCommandStepTester.java b/src/test/java/com/mathworks/ci/RunMatlabCommandStepTester.java new file mode 100644 index 00000000..ed51a0ae --- /dev/null +++ b/src/test/java/com/mathworks/ci/RunMatlabCommandStepTester.java @@ -0,0 +1,48 @@ +package com.mathworks.ci; + +/** + * Copyright 2020 The MathWorks, Inc. + * + */ + +import java.util.Set; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepDescriptor; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.DataBoundConstructor; +import com.google.common.collect.ImmutableSet; +import hudson.EnvVars; +import hudson.Extension; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Run; +import hudson.model.TaskListener; + +public class RunMatlabCommandStepTester extends RunMatlabCommandStep { + @DataBoundConstructor + public RunMatlabCommandStepTester(String command) { + super(command); + } + + @Override + public StepExecution start(StepContext context) throws Exception { + + return new TestStepExecution(context,this.getCommand()); + } + + @Extension + public static class CommandStepTestDescriptor extends StepDescriptor { + + @Override + public Set> getRequiredContext() { + return ImmutableSet.of(TaskListener.class, FilePath.class, Launcher.class, + EnvVars.class, Run.class); + } + + @Override + public String getFunctionName() { + return "testMATLABCommand"; + } + } + +} diff --git a/src/test/java/com/mathworks/ci/RunMatlabTestsStepTest.java b/src/test/java/com/mathworks/ci/RunMatlabTestsStepTest.java new file mode 100644 index 00000000..6501ea2f --- /dev/null +++ b/src/test/java/com/mathworks/ci/RunMatlabTestsStepTest.java @@ -0,0 +1,115 @@ +package com.mathworks.ci; + +/** + * Copyright 2020 The MathWorks, Inc. + * + */ + +import java.io.IOException; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import hudson.slaves.DumbSlave; + +public class RunMatlabTestsStepTest { + + private WorkflowJob project; + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Before + public void testSetup() throws IOException { + this.project = j.createProject(WorkflowJob.class); + } + + + /* + * Verify when MATLAB Path is not set + */ + @Test + public void verifyMATLABPathNotSet() throws Exception { + project.setDefinition(new CpsFlowDefinition( + "node {runMATLABTests(testResultsPDF:'myresult/result.pdf')}", true)); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("MATLAB_ROOT", build); + } + + + /* + * VErify when MATLAB PATH is set. + */ + + @Test + public void verifyMATLABPathSet() throws Exception { + project.setDefinition(new CpsFlowDefinition( + "node {testMATLABTests(testResultsPDF:'myresult/result.pdf')}", true)); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("tester_started", build); + } + + /* + * Verify Pipeline runs on slave node + */ + + @Test + public void verifyOnslave() throws Exception { + DumbSlave s = j.createOnlineSlave(); + project.setDefinition(new CpsFlowDefinition( + "node('!master') {testMATLABTests(testResultsPDF:'myresult/result.pdf')}", true)); + s.getWorkspaceFor(project); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertBuildStatusSuccess(build); + } + + /* + * Verify artifact path is correct. + */ + + @Test + public void verifyArtifactPath() throws Exception { + project.setDefinition(new CpsFlowDefinition( + "node {runMATLABTests(testResultsPDF:'myresult/result.pdf')}", true)); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("'PDFReportPath','myresult/result.pdf'", build); + } + + /* + * Verify Artifact is not sent as parameter if not selected in script. + */ + + @Test + public void verifyArtifactParameters() throws Exception { + project.setDefinition(new CpsFlowDefinition( + "node {runMATLABTests(testResultsPDF:'myresult/result.pdf')}", true)); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("'PDFReportPath','myresult/result.pdf'", build); + j.assertLogNotContains("TAPResultsPath", build); + j.assertLogNotContains("JUnitResultsPath", build); + j.assertLogNotContains("CoberturaCodeCoveragePath", build); + j.assertLogNotContains("SimulinkTestResultsPath", build); + j.assertLogNotContains("CoberturaModelCoveragePath", build); + } + + /* + * Verify runMatlabTests runs with empty parameters when nothing no artifact selected + */ + + @Test + public void verifyEmptyParameter() throws Exception { + project.setDefinition(new CpsFlowDefinition( + "node {runMATLABTests()}", true)); + WorkflowRun build = project.scheduleBuild2(0).get(); + j.assertLogContains("runMatlabTests()", build); + j.assertLogNotContains("PDFReportPath", build); + j.assertLogNotContains("TAPResultsPath", build); + j.assertLogNotContains("JUnitResultsPath", build); + j.assertLogNotContains("CoberturaCodeCoveragePath", build); + j.assertLogNotContains("SimulinkTestResultsPath", build); + j.assertLogNotContains("CoberturaModelCoveragePath", build); + } +} diff --git a/src/test/java/com/mathworks/ci/RunMatlabTestsStepTester.java b/src/test/java/com/mathworks/ci/RunMatlabTestsStepTester.java new file mode 100644 index 00000000..60a38311 --- /dev/null +++ b/src/test/java/com/mathworks/ci/RunMatlabTestsStepTester.java @@ -0,0 +1,72 @@ +package com.mathworks.ci; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepDescriptor; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.DataBoundConstructor; +import com.google.common.collect.ImmutableSet; +import hudson.EnvVars; +import hudson.Extension; +import hudson.FilePath; +import hudson.Launcher; +import hudson.model.Run; +import hudson.model.TaskListener; + +public class RunMatlabTestsStepTester extends RunMatlabTestsStep { + + + @DataBoundConstructor + public RunMatlabTestsStepTester() { + + } + + @Override + public StepExecution start(StepContext context) throws Exception { + Launcher launcher = context.get(Launcher.class); + FilePath workspace = context.get(FilePath.class); + + // Copy Scratch file needed to run MATLAB tests in workspace + FilePath targetWorkspace = new FilePath(launcher.getChannel(), workspace.getRemote()); + copyScratchFileInWorkspace(MatlabBuilderConstants.MATLAB_TESTS_RUNNER_RESOURCE, + MatlabBuilderConstants.MATLAB_TESTS_RUNNER_TARGET_FILE, targetWorkspace); + return new TestStepExecution(context, constructCommandForTest(getInputArgs())); + } + + private void copyScratchFileInWorkspace(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(0755); + } + + @Extension + public static class CommandStepTestDescriptor extends StepDescriptor { + + @Override + public Set> getRequiredContext() { + return ImmutableSet.of(TaskListener.class, FilePath.class, Launcher.class, + EnvVars.class, Run.class); + } + + @Override + public String getFunctionName() { + return "testMATLABTests"; + } + } + + public String getInputArgs() { + List args = Arrays.asList(getTestResultsPDF(), getTestResultsTAP(), + getTestResultsJUnit(), getTestResultsSimulinkTest(), getCodeCoverageCobertura(), + getModelCoverageCobertura()); + + return String.join(",", args); + } +} diff --git a/src/test/java/com/mathworks/ci/TestStepExecution.java b/src/test/java/com/mathworks/ci/TestStepExecution.java new file mode 100644 index 00000000..a0d79c7c --- /dev/null +++ b/src/test/java/com/mathworks/ci/TestStepExecution.java @@ -0,0 +1,48 @@ +package com.mathworks.ci; +/** + * Copyright 2020 The MathWorks, Inc. + * + */ + +import java.io.IOException; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; +import hudson.Launcher.ProcStarter; +import hudson.model.TaskListener; + +public class TestStepExecution extends MatlabRunTestsStepExecution { + + public TestStepExecution(StepContext context, String command) { + super(context, command); + + } + + @Override + public ProcStarter getProcessToRunMatlabCommand(FilePath workspace, Launcher launcher, + TaskListener listener, EnvVars envVars, String matlabCommand, String uniqueName) + throws IOException, InterruptedException { + // Get node specific tmp directory to copy matlab runner script + String tmpDir = getNodeSpecificTmpFolderPath(workspace); + FilePath targetWorkspace = new FilePath(launcher.getChannel(), tmpDir); + ProcStarter matlabLauncher; + if (launcher.isUnix()) { + final String runnerScriptName = uniqueName + "/run_matlab_command_test.sh"; + matlabLauncher = launcher.launch().pwd(workspace).envs(envVars) + .cmds(tmpDir + "/" + runnerScriptName, matlabCommand).stdout(listener); + + // Copy runner .sh for linux platform in workspace. + copyFileInWorkspace("run_matlab_command_test.sh", runnerScriptName, targetWorkspace); + } else { + final String runnerScriptName = uniqueName + "\\run_matlab_command_test.bat"; + launcher = launcher.decorateByPrefix("cmd.exe", "/C"); + matlabLauncher = launcher.launch().pwd(workspace).envs(envVars) + .cmds(tmpDir + "\\" + runnerScriptName, "\"" + matlabCommand + "\"") + .stdout(listener); + // Copy runner.bat for Windows platform in workspace. + copyFileInWorkspace("run_matlab_command_test.bat", runnerScriptName, targetWorkspace); + } + return matlabLauncher; + } +} diff --git a/src/test/resources/run_matlab_command_test.bat b/src/test/resources/run_matlab_command_test.bat new file mode 100755 index 00000000..f17d2e8a --- /dev/null +++ b/src/test/resources/run_matlab_command_test.bat @@ -0,0 +1,7 @@ +rem Copyright 2020 The MathWorks, Inc. + +echo "tester_started" + +set "arg1=%~1" + +echo "%arg1%" diff --git a/src/test/resources/run_matlab_command_test.sh b/src/test/resources/run_matlab_command_test.sh new file mode 100755 index 00000000..1d0ddcaa --- /dev/null +++ b/src/test/resources/run_matlab_command_test.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +#Copyright 2020 The MathWorks, Inc. + +echo "tester_started" +echo $1