diff --git a/HpToolsLauncher/HpToolsLauncher.csproj b/HpToolsLauncher/HpToolsLauncher.csproj
index 8114e537f..7a608b4df 100644
--- a/HpToolsLauncher/HpToolsLauncher.csproj
+++ b/HpToolsLauncher/HpToolsLauncher.csproj
@@ -89,6 +89,7 @@
+
diff --git a/HpToolsLauncher/Launcher.cs b/HpToolsLauncher/Launcher.cs
index 6836fbf7f..2bb9a2791 100644
--- a/HpToolsLauncher/Launcher.cs
+++ b/HpToolsLauncher/Launcher.cs
@@ -248,6 +248,36 @@ private IAssetRunner CreateRunner(bool isFirstRun, List reruntests = n
{
case TestStorageType.AlmLabManagement:
+ case TestStorageType.LoadRunner:
+ {
+ int timeout = 0;
+ if (_ciParams.ContainsKey("fsTimeout"))
+ {
+ int parsedTimeout;
+ if (int.TryParse(_ciParams["fsTimeout"], out parsedTimeout) && parsedTimeout != -1)
+ timeout = parsedTimeout;
+ }
+
+ string resultsDirectory = _ciParams["fsReportPath"].ToString();
+ if (!Directory.Exists(resultsDirectory))
+ {
+ Console.WriteLine("The provided scenario folder path {0} does not exist.", resultsDirectory);
+ Environment.Exit((int)ExitCodeEnum.Failed);
+ }
+ string testPath = _ciParams["Test1"].ToString();
+ if (!File.Exists(testPath))
+ {
+ Console.WriteLine("The provided scenario folder path {0} does not exist.", testPath);
+ Environment.Exit((int)ExitCodeEnum.Failed);
+ }
+
+ ServiceTestRunner svc = new ServiceTestRunner();
+ bool result = svc.RunServiceTest(testPath, resultsDirectory, timeout);
+
+ Environment.Exit(result ? (int)ExitCodeEnum.Passed : (int)ExitCodeEnum.Failed);
+ break;
+ }
+
case TestStorageType.Alm:
{
//check that all required parameters exist
diff --git a/HpToolsLauncher/TestRunners/ServiceTestRunner.cs b/HpToolsLauncher/TestRunners/ServiceTestRunner.cs
new file mode 100644
index 000000000..5443930f2
--- /dev/null
+++ b/HpToolsLauncher/TestRunners/ServiceTestRunner.cs
@@ -0,0 +1,211 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+
+namespace HpToolsLauncher.TestRunners
+{
+ public class ServiceTestRunner
+ {
+ //
+ public string binPath = Environment.ExpandEnvironmentVariables("%LR_PATH%bin");
+ public string[] processesToKill = new string[] { "wlrun", "lrun_svc", "CmdServiceClient" };
+ public int _timeout;
+
+ public bool RunServiceTest(string testPath, string resultsDirectory, int timeout)
+ {
+ _timeout = timeout * 1000;
+ Cleanup(processesToKill);
+ LogMessage("Cleanup complete.");
+
+ //Start lrun_svc.exe and CmdServiceClient.exe
+ StartLrunSvc();
+ LogMessage("lrun_svc.exe is running");
+ Process client = StartClient();
+ LogMessage("CmdServiceClient.exe is running");
+
+ //Set the load test data to the given .lrs
+ string command = "setLoadTestData " + testPath;
+ Write(client, command);
+ LogMessage("Command given:",command);
+
+ string result = Read(client);
+ LogMessage("Client response:", result);
+ if ((result.Contains("failed") || (result.Contains("empty"))))
+ {
+ Cleanup(processesToKill);
+ return false;
+ }
+
+
+ //Set the results folder directory
+ string command1 ="setResultsDirectory " + resultsDirectory;
+ Write(client, command1);
+ LogMessage("Command given:", command1);
+
+ result = Read(client);
+ LogMessage("Client response:",result);
+ if ((result.Contains("failed") || (result.Contains("empty"))))
+ {
+ Cleanup(processesToKill);
+ return false;
+ }
+
+ //Start the load test
+ Write(client,"startLoadTest");
+ LogMessage("Command given: startLoadTest");
+
+ result = Read(client);
+ LogMessage("Client response:",result);
+ if (result.Contains("failed"))
+ {
+ Cleanup(processesToKill);
+ return false;
+ }
+ LogMessage("The test has started, waiting for the test to end.");
+ Stopwatch stopWatch = new Stopwatch();
+ stopWatch.Start();
+ var startTime= DateTime.Now;
+
+ //Get the result
+ result = "";
+ while (!result.Contains("Ended")) //wait for the test to end
+ {
+ Write(client, "getServiceState"); //ready, collating, running, ended
+ result = Read(client);
+ if ((result.Contains("failed") || (result.Contains("empty"))))
+ {
+ LogMessage(result);
+ Cleanup(processesToKill);
+ return false;
+ }
+ Thread.Sleep(1);
+ }
+
+ stopWatch.Stop();
+ TimeSpan ts = stopWatch.Elapsed;
+ string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
+ ts.Hours, ts.Minutes, ts.Seconds,
+ ts.Milliseconds / 10);
+ LogMessage("Test completed in", elapsedTime);
+ Dispose(client);
+ Cleanup(processesToKill);
+ return true;
+ }
+
+ #region Process utilities
+ public void StartLrunSvc()
+ {
+ LogMessage("Starting lrun_svc.exe...");
+ var startInfo = new System.Diagnostics.ProcessStartInfo
+ {
+ WorkingDirectory = binPath,
+ FileName = "lrun_svc.exe",
+ };
+ var proc = new Process
+ {
+ StartInfo = startInfo
+ };
+ proc.Start();
+ System.Threading.Thread.Sleep(5000);
+ }
+
+ public Process StartClient()
+ {
+ var startInfo = new System.Diagnostics.ProcessStartInfo
+ {
+ FileName = binPath + @"\CmdServiceClient.exe",
+ RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ UseShellExecute = false
+ };
+
+ var proc = new Process
+ {
+ StartInfo = startInfo
+ };
+ proc.Start();
+ return proc;
+ }
+
+ public void Cleanup(string[] processes)
+ {
+ foreach (string process in processes)
+ {
+ Process[] workers = Process.GetProcessesByName(process);
+ foreach (Process worker in workers)
+ {
+ try
+ {
+ worker.Kill();
+ worker.WaitForExit();
+ worker.Dispose();
+ }
+ catch (UnauthorizedAccessException)
+ {
+ continue;
+ }
+
+ }
+ }
+
+ }
+
+ public void LogMessage(string message)
+ {
+ Console.WriteLine("[{0}] {1}", DateTime.Now.ToString("h:mm:ss tt"), message);
+ }
+
+ public void LogMessage(string message, string extraInfo)
+ {
+ Console.WriteLine("[{0}] {1} {2}", DateTime.Now.ToString("h:mm:ss tt"), message, extraInfo);
+ }
+ #endregion
+
+ #region Client communication
+ public void Write(Process client, string command)
+ {
+ StreamWriter writer = client.StandardInput;
+ writer.WriteLine(command);
+ writer.Flush();
+ }
+ public void Write(Process client, string command, int timeout)
+ {
+ StreamWriter writer = client.StandardInput;
+ writer.WriteLine(command);
+ //System.Threading.Thread.Sleep(timeout);
+ writer.Flush();
+ }
+ public string ReadLine(Process client)
+ {
+
+ StreamReader reader = client.StandardOutput;
+ string result = "";
+ do
+ {
+ int output = reader.Read();
+ result += (char)output;
+ }
+ while (reader.Peek() > -1);
+ reader.DiscardBufferedData();
+ return result;
+ }
+ public void Dispose(Process client)
+ {
+ client.StandardInput.Close();
+ client.StandardOutput.Close();
+ client.Dispose();
+ }
+ public string Read(Process client)
+ {
+ string result = ReadLine(client);
+ while (result.Length < 3)
+ {
+ result =ReadLine(client);
+ System.Threading.Thread.Sleep(5);
+ }
+ return result;
+ }
+ }
+ #endregion
+}
diff --git a/HpToolsLauncher/app.config b/HpToolsLauncher/app.config
index 6e39bb5ce..c05a491e4 100644
--- a/HpToolsLauncher/app.config
+++ b/HpToolsLauncher/app.config
@@ -1,6 +1,6 @@
-
+
diff --git a/src/main/java/com/microfocus/application/automation/tools/model/RunFromServiceModel.java b/src/main/java/com/microfocus/application/automation/tools/model/RunFromServiceModel.java
new file mode 100644
index 000000000..24b4dd627
--- /dev/null
+++ b/src/main/java/com/microfocus/application/automation/tools/model/RunFromServiceModel.java
@@ -0,0 +1,180 @@
+/*
+ * Certain versions of software and/or documents ("Material") accessible here may contain branding from
+ * Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company. As of September 1, 2017,
+ * the Material is now offered by Micro Focus, a separately owned and operated company. Any reference to the HP
+ * and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE
+ * marks are the property of their respective owners.
+ * __________________________________________________________________
+ * MIT License
+ *
+ * (c) Copyright 2012-2023 Micro Focus or one of its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * ___________________________________________________________________
+ */
+
+package com.microfocus.application.automation.tools.model;
+
+import com.microfocus.application.automation.tools.uft.utils.UftToolUtils;
+import hudson.EnvVars;
+import hudson.Extension;
+import hudson.model.AbstractDescribableImpl;
+import hudson.model.Descriptor;
+import hudson.model.Node;
+import org.apache.commons.lang.StringUtils;
+import org.kohsuke.stapler.DataBoundConstructor;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Properties;
+
+/**
+ * Holds the data for RunFromFile build type.
+ */
+public class RunFromServiceModel extends AbstractDescribableImpl {
+
+ private String fsTests;
+ private String fsTimeout;
+ private String fsReportPath;
+
+ /**
+ * Instantiates a new Run from file system model.
+ *
+ * @param fsTests the fs tests path
+ * @param fsTimeout the fs timeout in minutes for tests in seconds
+ * @param fsReportPath the fs report path
+
+ */
+ @DataBoundConstructor
+ public RunFromServiceModel(String fsTests, String fsTimeout, String fsReportPath) {
+ this.setFsTests(fsTests);
+ this.fsTimeout = fsTimeout;
+ this.fsReportPath = fsReportPath;
+ }
+
+
+ /**
+ * Sets fs tests.
+ *
+ * @param fsTests the fs tests
+ */
+ public void setFsTests(String fsTests) {
+ this.fsTests = fsTests.trim();
+
+ if (!this.fsTests.contains("\n")) {
+ this.fsTests += "\n";
+ }
+ }
+
+ /**
+ * Sets fs timeout.
+ *
+ * @param fsTimeout the fs timeout
+ */
+ public void setFsTimeout(String fsTimeout) {
+ this.fsTimeout = fsTimeout;
+ }
+
+ /**
+ * Gets fs tests.
+ *
+ * @return the fs tests
+ */
+ public String getFsTests() {
+ return fsTests;
+ }
+
+ /**
+ * Gets fs timeout.
+ *
+ * @return the fs timeout
+ */
+ public String getFsTimeout() {
+ return fsTimeout;
+ }
+
+ /**
+ * Sets the report path for the given tests.
+ */
+ public void setFsReportPath(String fsReportPath) {
+ this.fsReportPath = fsReportPath;
+ }
+
+ /**
+ * Gets the test report path.
+ */
+ public String getFsReportPath() {
+ return fsReportPath;
+ }
+
+ /**
+ * Gets properties.
+ *
+ * @param envVars the env vars
+ * @return the properties
+ */
+ @Nullable
+ public Properties getProperties(EnvVars envVars, Node currNode) {
+ return createProperties(envVars, currNode);
+ }
+
+ private Properties createProperties(EnvVars envVars, Node currNode) {
+ Properties props = new Properties();
+
+ addTestsToProps(envVars, props);
+
+ return props;
+ }
+
+
+ private void addTestsToProps(EnvVars envVars, Properties props) {
+ String fsTimeoutVal = StringUtils.isEmpty(fsTimeout) ? "-1" : envVars.expand(fsTimeout);
+ props.put("fsTimeout", fsTimeoutVal);
+
+ if (StringUtils.isNotBlank(fsReportPath)) {
+ props.put("fsReportPath", fsReportPath);
+ }
+ if (!StringUtils.isEmpty(this.fsTests)) {
+ String expandedFsTests = envVars.expand(fsTests);
+ String[] testsArr;
+ if (UftToolUtils.isMtbxContent(expandedFsTests)) {
+ testsArr = new String[]{expandedFsTests};
+ } else {
+ testsArr = expandedFsTests.replaceAll("\r", "").split("\n");
+ }
+
+ int i = 1;
+
+ for (String test : testsArr) {
+ test = test.trim();
+ props.put("Test" + i, test);
+ i++;
+ }
+ } else {
+ props.put("fsTests", "");
+ }
+ }
+
+
+ @Extension
+ public static class DescriptorImpl extends Descriptor {
+ @Nonnull
+ @Override
+ public String getDisplayName() {
+ return "LR Service File System Model";
+ }
+ }
+}
diff --git a/src/main/java/com/microfocus/application/automation/tools/run/RunFromService.java b/src/main/java/com/microfocus/application/automation/tools/run/RunFromService.java
new file mode 100644
index 000000000..6d9e10a92
--- /dev/null
+++ b/src/main/java/com/microfocus/application/automation/tools/run/RunFromService.java
@@ -0,0 +1,490 @@
+/*
+ * Certain versions of software and/or documents ("Material") accessible here may contain branding from
+ * Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company. As of September 1, 2017,
+ * the Material is now offered by Micro Focus, a separately owned and operated company. Any reference to the HP
+ * and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE
+ * marks are the property of their respective owners.
+ * __________________________________________________________________
+ * MIT License
+ *
+ * (c) Copyright 2012-2023 Micro Focus or one of its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+ * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * ___________________________________________________________________
+ */
+
+package com.microfocus.application.automation.tools.run;
+
+import com.hp.octane.integrations.executor.TestsToRunConverter;
+import com.microfocus.application.automation.tools.JenkinsUtils;
+import com.microfocus.application.automation.tools.AlmToolsUtils;
+import com.microfocus.application.automation.tools.EncryptionUtils;
+import com.microfocus.application.automation.tools.Messages;
+import com.microfocus.application.automation.tools.model.*;
+import com.microfocus.application.automation.tools.uft.model.UftRunAsUser;
+import com.microfocus.application.automation.tools.uft.utils.UftToolUtils;
+import hudson.*;
+import hudson.model.*;
+import hudson.tasks.BuildStepDescriptor;
+import hudson.tasks.Builder;
+import hudson.util.FormValidation;
+import hudson.util.IOUtils;
+import hudson.util.VariableResolver;
+import jenkins.model.Jenkins;
+import jenkins.tasks.SimpleBuildStep;
+import net.minidev.json.JSONObject;
+import org.apache.commons.lang.StringUtils;
+import org.jenkinsci.Symbol;
+import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.DataBoundSetter;
+import org.kohsuke.stapler.QueryParameter;
+
+import javax.annotation.Nonnull;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.URL;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Describes a regular jenkins build step from UFT or LR
+ */
+public class RunFromService extends Builder implements SimpleBuildStep {
+
+ private static final String LRANALYSIS_LAUNCHER_EXE = "LRAnalysisLauncher.exe";
+
+ public static final String HP_TOOLS_LAUNCHER_EXE = "HpToolsLauncher.exe";
+ public static final String HP_TOOLS_LAUNCHER_EXE_CFG = "HpToolsLauncher.exe.config";
+
+ private String ResultFilename = "ApiResults.xml";
+
+ private String ParamFileName = "ApiRun.txt";
+
+ private RunFromServiceModel runFromServiceModel;
+
+ private Map resultFileNames;
+
+
+ // /**
+ // * Instantiates a new Run from file builder.
+ // *
+ // * @param fsTests the fs tests
+ // */
+ // public RunFromService(String fsTests) {
+ // runFromServiceModel = new RunFromServiceModel(fsTests);
+ // }
+
+ // /**
+ // * Instantiates a new Run from file builder.
+ // *
+ // * @param runFromServiceModel the run from file model
+ // */
+ // public RunFromService(RunFromServiceModel runFromServiceModel) {
+ // this.runFromServiceModel = runFromServiceModel;
+ // }
+
+ @DataBoundConstructor
+ public RunFromService(RunFromServiceModel runFromSm) {
+ runFromServiceModel = runFromSm;
+ }
+
+ public String getFsTimeout() {
+ return runFromServiceModel.getFsTimeout();
+ }
+
+ /**
+ * Sets fs timeout.
+ *
+ * @param fsTimeout the fs timeout
+ */
+ @DataBoundSetter
+ public void setFsTimeout(String fsTimeout) {
+ runFromServiceModel.setFsTimeout(fsTimeout);
+ }
+
+ public String getFsTests() {
+ return runFromServiceModel.getFsTests();
+ }
+
+ public void setFsTests(String fsTests) {
+ runFromServiceModel.setFsTests(fsTests);
+ }
+
+ /**
+ * Get the fs report path.
+ *
+ * @return the filesystem report path
+ */
+ public String getFsReportPath() {
+ return runFromServiceModel.getFsReportPath();
+ }
+
+ /**
+ * Sets the report path
+ *
+ * @param fsReportPath the report path
+ */
+ @DataBoundSetter
+ public void setFsReportPath(String fsReportPath) {
+ runFromServiceModel.setFsReportPath(fsReportPath);
+ }
+
+
+ public Map getResultFileNames() {
+ return resultFileNames;
+ }
+
+ @DataBoundSetter
+ public void setResultFileNames(Map results) {
+ resultFileNames = results;
+ }
+
+ @Override
+ public void perform(@Nonnull Run, ?> build, @Nonnull FilePath workspace, @Nonnull Launcher launcher,
+ @Nonnull TaskListener listener)
+ throws IOException {
+ PrintStream out = listener.getLogger();
+
+ UftOctaneUtils.setUFTRunnerTypeAsParameter(build, listener);
+
+ EnvVars env = null;
+ try {
+ env = build.getEnvironment(listener);
+ } catch (IOException | InterruptedException e) {
+ listener.error("Failed loading build environment: " + e.getMessage());
+ }
+
+ Node currNode = JenkinsUtils.getCurrentNode(workspace);
+ if (currNode == null) {
+ listener.error("Failed to get current executor node.");
+ return;
+ }
+
+ // in case of mbt, since mbt can support uft and codeless at the same time, run only if there are uft tests
+ ParametersAction parameterAction = build.getAction(ParametersAction.class);
+ ParameterValue octaneFrameworkParam = parameterAction != null ? parameterAction.getParameter("octaneTestRunnerFramework") : null;
+ if (octaneFrameworkParam != null && octaneFrameworkParam.getValue().equals("MBT")) {
+ String testsToRunConverted = env == null ? null : env.get(TestsToRunConverter.DEFAULT_TESTS_TO_RUN_CONVERTED_PARAMETER);
+ if (StringUtils.isEmpty(testsToRunConverted)) {
+ out.println(RunFromService.class.getSimpleName() + " : No UFT tests were found");
+ return;
+ }
+ }
+
+ // this is an unproper replacement to the build.getVariableResolver since workflow run won't support the
+ // getBuildEnvironment() as written here:
+ // https://github.com/jenkinsci/pipeline-plugin/blob/893e3484a25289c59567c6724f7ce19e3d23c6ee/DEVGUIDE
+ // .md#variable-substitutions
+
+ JSONObject jobDetails;
+ // now merge them into one list
+ Properties mergedProperties = new Properties();
+
+
+ if (env == null) {
+ listener.fatalError("Environment not set");
+ throw new IOException("Env Null - something went wrong with fetching jenkins build environment");
+ }
+
+ if (build instanceof AbstractBuild) {
+ VariableResolver varResolver = ((AbstractBuild) build).getBuildVariableResolver();
+ }
+
+ mergedProperties.putAll(Objects.requireNonNull(runFromServiceModel).getProperties(env, currNode));
+
+ boolean isPrintTestParams = UftToolUtils.isPrintTestParams(build, listener);
+ mergedProperties.put("printTestParams", isPrintTestParams ? "1" : "0");
+
+ UftRunAsUser uftRunAsUser;
+ try {
+ uftRunAsUser = UftToolUtils.getRunAsUser(build, listener);
+ if (uftRunAsUser != null) {
+ mergedProperties.put("uftRunAsUserName", uftRunAsUser.getUsername());
+ if (StringUtils.isNotBlank(uftRunAsUser.getEncodedPassword())) {
+ mergedProperties.put("uftRunAsUserEncodedPassword", uftRunAsUser.getEncodedPasswordAsEncrypted(currNode));
+ } else if (uftRunAsUser.getPassword() != null) {
+ mergedProperties.put("uftRunAsUserPassword", uftRunAsUser.getPasswordAsEncrypted(currNode));
+ }
+ }
+ } catch(IllegalArgumentException | EncryptionUtils.EncryptionException e) {
+ build.setResult(Result.FAILURE);
+ listener.fatalError(String.format("Build parameters check failed: %s.", e.getMessage()));
+ return;
+ }
+ int idx = 0;
+ for (Iterator iterator = env.keySet().iterator(); iterator.hasNext(); ) {
+ String key = iterator.next();
+ idx++;
+ mergedProperties.put("JenkinsEnv" + idx, key + ";" + env.get(key));
+ }
+
+ Date now = new Date();
+ Format formatter = new SimpleDateFormat("ddMMyyyyHHmmssSSS");
+ String time = formatter.format(now);
+
+ // get a unique filename for the params file
+ ParamFileName = "props" + time + ".txt";
+ ResultFilename = String.format("Results%s_%d.xml", time, build.getNumber());
+
+ long threadId = Thread.currentThread().getId();
+ if (resultFileNames == null) {
+ resultFileNames = new HashMap();
+ }
+ resultFileNames.put(threadId, ResultFilename);
+
+ mergedProperties.put("runType", AlmRunTypes.RunType.LoadRunner.toString());
+
+ mergedProperties.put("resultsFilename", ResultFilename);
+
+
+ // cleanup report folders before running the build
+ String selectedNode = env.get("NODE_NAME");
+ if (selectedNode == null) {//if slave is given in the pipeline and not as part of build step
+ try {
+ selectedNode = launcher.getComputer().getName();
+ } catch (Exception e) {
+ listener.error("Failed to get selected node for UFT execution : " + e.getMessage());
+ }
+ }
+
+ // clean cleanuptests' report folders
+ int index = 1;
+ while (mergedProperties.getProperty("CleanupTest" + index) != null) {
+ String testPath = mergedProperties.getProperty("CleanupTest" + index);
+ List cleanupTests = UftToolUtils.getBuildTests(selectedNode, testPath);
+ for (String test : cleanupTests) {
+ UftToolUtils.deleteReportFoldersFromNode(selectedNode, test, listener);
+ }
+
+ index++;
+ }
+
+ // clean actual tests' report folders
+ index = 1;
+ while (mergedProperties.getProperty("Test" + index) != null) {
+ String testPath = mergedProperties.getProperty(("Test" + index));
+ List buildTests = UftToolUtils.getBuildTests(selectedNode, testPath);
+ for (String test : buildTests) {
+ UftToolUtils.deleteReportFoldersFromNode(selectedNode, test, listener);
+ }
+ index++;
+ }
+
+ mergedProperties.setProperty("numOfTests", String.valueOf(index - 1));
+
+ // get properties serialized into a stream
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ mergedProperties.store(stream, "");
+ } catch (IOException e) {
+ listener.error("Storing run variable failed: " + e);
+ build.setResult(Result.FAILURE);
+ }
+
+ String propsSerialization = stream.toString();
+
+ FilePath CmdLineExe;
+ try (InputStream propsStream = IOUtils.toInputStream(propsSerialization)) {
+ // Get the URL to the Script used to run the test, which is bundled
+ // in the plugin
+ @SuppressWarnings("squid:S2259")
+ URL cmdExeUrl = Jenkins.get().pluginManager.uberClassLoader.getResource(HP_TOOLS_LAUNCHER_EXE);
+ if (cmdExeUrl == null) {
+ listener.fatalError(HP_TOOLS_LAUNCHER_EXE + " not found in resources");
+ return;
+ }
+
+ @SuppressWarnings("squid:S2259")
+ URL cmdExeCfgUrl = Jenkins.get().pluginManager.uberClassLoader.getResource(HP_TOOLS_LAUNCHER_EXE_CFG);
+ if (cmdExeCfgUrl == null) {
+ listener.fatalError(HP_TOOLS_LAUNCHER_EXE_CFG + " not found in resources");
+ return;
+ }
+
+ @SuppressWarnings("squid:S2259")
+ URL cmdExe2Url = Jenkins.get().pluginManager.uberClassLoader.getResource(LRANALYSIS_LAUNCHER_EXE);
+ if (cmdExe2Url == null) {
+ listener.fatalError(LRANALYSIS_LAUNCHER_EXE + "not found in resources");
+ return;
+ }
+
+ FilePath propsFileName = workspace.child(ParamFileName);
+ CmdLineExe = workspace.child(HP_TOOLS_LAUNCHER_EXE);
+ FilePath CmdLineExeCfg = workspace.child(HP_TOOLS_LAUNCHER_EXE_CFG);
+ FilePath CmdLineExe2 = workspace.child(LRANALYSIS_LAUNCHER_EXE);
+
+ try {
+ // create a file for the properties file, and save the properties
+ propsFileName.copyFrom(propsStream);
+ // Copy the script to the project workspace
+ CmdLineExe.copyFrom(cmdExeUrl);
+ CmdLineExeCfg.copyFrom(cmdExeCfgUrl);
+ CmdLineExe2.copyFrom(cmdExe2Url);
+ } catch (IOException | InterruptedException e) {
+ build.setResult(Result.FAILURE);
+ listener.error("Copying executable files to executing node " + e);
+ }
+ }
+
+ try {
+ // Run the HpToolsLauncher.exe
+ AlmToolsUtils.runOnBuildEnv(build, launcher, listener, CmdLineExe, ParamFileName, currNode);
+ // Has the report been successfully generated?
+ } catch (IOException ioe) {
+ Util.displayIOException(ioe, listener);
+ build.setResult(Result.FAILURE);
+ listener.error("Failed running HpToolsLauncher " + ioe.getMessage());
+ } catch (InterruptedException e) {
+ build.setResult(Result.ABORTED);
+ listener.error("Failed running HpToolsLauncher - build aborted " + StringUtils.defaultString(e.getMessage()));
+ try {
+ AlmToolsUtils.runHpToolsAborterOnBuildEnv(build, launcher, listener, ParamFileName, workspace);
+ } catch (IOException e1) {
+ Util.displayIOException(e1, listener);
+ build.setResult(Result.FAILURE);
+ } catch (InterruptedException e1) {
+ listener.error("Failed running HpToolsAborter " + e1.getMessage());
+ }
+ }
+ }
+
+
+ @Override
+ public DescriptorImpl getDescriptor() {
+ return (DescriptorImpl) super.getDescriptor();
+ }
+
+ /**
+ * Gets run from file model.
+ *
+ * @return the run from file model
+ */
+ public RunFromServiceModel getrunFromServiceModel() {
+ return runFromServiceModel;
+ }
+
+ /**
+ * Gets run results file name.
+ *
+ * @return the run results file name
+ */
+ public String getRunResultsFileName() {
+ synchronized (this) {
+ long threadId = Thread.currentThread().getId();
+ String fileName = resultFileNames.get(threadId);
+ return fileName;
+ }
+ }
+
+
+ /**
+ * The type Descriptor.
+ */
+ @Symbol("runFromServiceBuilder")
+ @Extension
+ public static final class DescriptorImpl extends BuildStepDescriptor {
+
+ /**
+ * Instantiates a new Descriptor.
+ */
+ public DescriptorImpl() {
+ load();
+ }
+
+ @Override
+ public boolean isApplicable(
+ @SuppressWarnings("rawtypes") Class extends AbstractProject> jobType) {
+ return true;
+ }
+
+
+ @Override
+ public String getDisplayName() {
+ return Messages.RunFromFileBuilderStepName(Messages.LrunSvc());
+ }
+
+ /**
+ * Do check fs tests form validation.
+ *
+ * @param value the value
+ * @return the form validation
+ */
+ @SuppressWarnings("squid:S1172")
+ public FormValidation doCheckFsTests(@QueryParameter String value) {
+ //TODO
+ return FormValidation.ok();
+ }
+
+ /**
+ * Do check ignore error strings form validation.
+ *
+ * @param value the value
+ * @return the form validation
+ */
+ @SuppressWarnings("squid:S1172")
+ public FormValidation doCheckIgnoreErrorStrings(@QueryParameter String value) {
+
+ return FormValidation.ok();
+ }
+
+ /**
+ * Do check fs timeout form validation.
+ *
+ * @param value the value
+ * @return the form validation
+ */
+ public FormValidation doCheckFsTimeout(@QueryParameter String value) {
+ if (StringUtils.isEmpty(value)) {
+ return FormValidation.ok();
+ }
+
+ String sanitizedValue = value.trim();
+ if (sanitizedValue.length() > 0 && sanitizedValue.charAt(0) == '-') {
+ sanitizedValue = sanitizedValue.substring(1);
+ }
+
+ if (!isParameterizedValue(sanitizedValue) && !StringUtils.isNumeric(sanitizedValue)) {
+ return FormValidation.error("Timeout must be a parameter or a number, e.g.: 23, $Timeout or " +
+ "${Timeout}.");
+ }
+
+ return FormValidation.ok();
+ }
+
+
+ /**
+ * Check if the value is parameterized.
+ *
+ * @param value the value
+ * @return boolean
+ */
+ public boolean isParameterizedValue(String value) {
+ //Parameter (with or without brackets)
+ return value.matches("^\\$\\{[\\w-. ]*}$|^\\$[\\w-.]*$");
+ }
+
+
+ public List getNodes() {
+ return UftToolUtils.getNodesList();
+ }
+
+ // public List getEncodings() { return RunFromServiceModel.encodings; }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/com/microfocus/application/automation/tools/Messages.properties b/src/main/resources/com/microfocus/application/automation/tools/Messages.properties
index 9d596f4d0..036d0d9b7 100644
--- a/src/main/resources/com/microfocus/application/automation/tools/Messages.properties
+++ b/src/main/resources/com/microfocus/application/automation/tools/Messages.properties
@@ -31,6 +31,7 @@
#
CompanyName=OpenText
+LrunSvc=Service
RunFromAlmBuilderStepName=Execute {0} functional tests from {0} ALM
SseBuilderStepName=Execute {0} tests using {0} ALM Lab Management
AutEnvironmentBuilderStepName=Execute AUT Environment preparation using {0} ALM Lab Management
diff --git a/src/main/resources/com/microfocus/application/automation/tools/run/RunFromService/config.jelly b/src/main/resources/com/microfocus/application/automation/tools/run/RunFromService/config.jelly
new file mode 100644
index 000000000..04b878944
--- /dev/null
+++ b/src/main/resources/com/microfocus/application/automation/tools/run/RunFromService/config.jelly
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${%DontForgetThePublisherFreestyle}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/com/microfocus/application/automation/tools/run/RunFromService/config.properties b/src/main/resources/com/microfocus/application/automation/tools/run/RunFromService/config.properties
new file mode 100644
index 000000000..84a9e8c28
--- /dev/null
+++ b/src/main/resources/com/microfocus/application/automation/tools/run/RunFromService/config.properties
@@ -0,0 +1,35 @@
+#
+# Certain versions of software and/or documents ("Material") accessible here may contain branding from
+# Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company. As of September 1, 2017,
+# the Material is now offered by OpenText, a separately owned and operated company. Any reference to the HP
+# and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE
+# marks are the property of their respective owners.
+# __________________________________________________________________
+# MIT License
+#
+# (c) Copyright 2012-2024 OpenText or one of its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or
+# substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# ___________________________________________________________________
+#
+
+DontForgetThePublisherFreestyle=Make sure to enable the Publish OpenText \
+ tests result option in the Post-build \
+ Actions section. This allows the tests results to be published.
+DontForgetThePublisherPipeline=We suggest adding a Publish OpenText \
+ tests result step in the pipeline job. This allows the tests results to be published.
+SummaryDataLog=Summary Data Log
+RuntimeSettings=Runtime Settings
\ No newline at end of file