Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated code lifecycle: Configure checkout path and timeout for programming exercises #9217

Open
wants to merge 67 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
f1c4e33
Add custom checkout path and timeout (server and client)
BBesrour Aug 14, 2024
104aa48
fix wrong key
BBesrour Aug 14, 2024
b27e5b5
fix style
BBesrour Aug 14, 2024
a74d607
fix style and client tests
BBesrour Aug 14, 2024
9763c6f
remove from aeolus comp and add tests
BBesrour Aug 15, 2024
f7ef62c
remove checkout path implementation
BBesrour Aug 17, 2024
011b1ab
fix client tests
BBesrour Aug 17, 2024
60f1c57
update java
BBesrour Aug 18, 2024
d5f13d5
update java
BBesrour Aug 18, 2024
c944bd4
update python
BBesrour Aug 18, 2024
e8010cc
update python
BBesrour Aug 18, 2024
769a121
update c
BBesrour Aug 18, 2024
31c33e1
update haskell
BBesrour Aug 18, 2024
c8e1cea
update kotlin
BBesrour Aug 18, 2024
0b4cc01
update vhdl and assembler
BBesrour Aug 19, 2024
1df6e4a
update ocaml and swift
BBesrour Aug 19, 2024
e4472b2
fix style and add button
BBesrour Aug 19, 2024
2e43d3c
ui
BBesrour Aug 19, 2024
023f1cd
ui
BBesrour Aug 20, 2024
aa99858
ui
BBesrour Aug 20, 2024
bce34f5
changes
BBesrour Aug 20, 2024
9984fe7
changes
BBesrour Aug 20, 2024
15a5753
changes
BBesrour Aug 20, 2024
f9208e6
changes
BBesrour Aug 21, 2024
6c20206
changes
BBesrour Aug 21, 2024
0511f72
add warning and fix tests
BBesrour Aug 21, 2024
296d71a
add client tests
BBesrour Aug 21, 2024
14cf069
fix ui
BBesrour Aug 21, 2024
4ba2f48
fix ui
BBesrour Aug 21, 2024
80e6612
fix ui
BBesrour Aug 21, 2024
2f55f93
update tests
BBesrour Aug 21, 2024
c464390
update tests
BBesrour Aug 21, 2024
0b05965
update tests
BBesrour Aug 21, 2024
9098588
Merge branch 'refs/heads/develop' into feature/integrated-code-lifecy…
BBesrour Aug 21, 2024
ae263ef
fix naming
BBesrour Aug 21, 2024
cd5c862
avoid unnecessary calls
BBesrour Aug 22, 2024
a742ec9
Merge branch 'refs/heads/develop' into feature/integrated-code-lifecy…
BBesrour Sep 4, 2024
cc5f77f
fix build
BBesrour Sep 4, 2024
4ac333e
fix build
BBesrour Sep 4, 2024
3589db8
Merge branch 'develop' into feature/integrated-code-lifecycle/custom-…
BBesrour Sep 4, 2024
6008a35
add server side validation
BBesrour Sep 5, 2024
1919b8f
fix validation and add tests
BBesrour Sep 6, 2024
f7db4b6
remove todo
BBesrour Sep 6, 2024
2a4b8a6
fix style
BBesrour Sep 7, 2024
7c9948f
- add warning
BBesrour Sep 7, 2024
ad24a7c
Allow to edit checkout paths only at creation
BBesrour Sep 8, 2024
a830953
remove unnecessary changes
BBesrour Sep 8, 2024
f383c48
fix issue displaying checkout directories
BBesrour Sep 8, 2024
0a42378
fix tests
BBesrour Sep 9, 2024
bc8c8ca
Merge branch 'refs/heads/develop' into feature/integrated-code-lifecy…
BBesrour Sep 12, 2024
8a3135d
Merge branch 'refs/heads/develop' into feature/integrated-code-lifecy…
BBesrour Sep 12, 2024
edd6d25
fix build
BBesrour Sep 12, 2024
d515c87
fix build script not updating after test checkout path changed
BBesrour Sep 12, 2024
4bd7382
fix issue when editing exercise
BBesrour Sep 12, 2024
7d7affc
fix aux repos not being created
BBesrour Sep 12, 2024
5b21610
fix client tests
Sep 12, 2024
ed19985
Merge branch 'develop' into feature/integrated-code-lifecycle/custom-…
BBesrour Sep 12, 2024
4d4b901
fix issue without buildscript not being editable
BBesrour Sep 13, 2024
689d7cc
use new angular guidelines
BBesrour Sep 14, 2024
e80162f
Merge branch 'develop' into feature/integrated-code-lifecycle/custom-…
BBesrour Sep 14, 2024
2b2fe08
fix tests and remove unused function
BBesrour Sep 14, 2024
06d0586
feedback
BBesrour Sep 19, 2024
4fee385
Merge branch 'develop' into feature/integrated-code-lifecycle/custom-…
BBesrour Sep 20, 2024
51bfcc8
merge conflict
BBesrour Sep 20, 2024
6e0b361
adjust javascript template
BBesrour Sep 21, 2024
81eb283
coderabbit feedback
BBesrour Sep 21, 2024
f54e964
Merge branch 'develop' into feature/integrated-code-lifecycle/custom-…
BBesrour Sep 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record BuildConfig(String buildScript, String dockerImage, String commitHashToBuild, String assignmentCommitHash, String testCommitHash, String branch,
ProgrammingLanguage programmingLanguage, ProjectType projectType, boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled,
List<String> resultPaths) implements Serializable {
List<String> resultPaths, int timeoutSeconds, String assignmentCheckoutPath, String testCheckoutPath, String solutionCheckoutPath) implements Serializable {

@Override
public String dockerImage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -275,11 +276,22 @@ public String getIDOfRunningContainer(String containerName) {
* @param auxiliaryRepositoriesPaths An array of paths for auxiliary repositories to be included in the build process.
* @param auxiliaryRepositoryCheckoutDirectories An array of directory names within the container where each auxiliary repository should be checked out.
* @param programmingLanguage The programming language of the repositories, which influences directory naming conventions.
* @param assignmentCheckoutPath The directory within the container where the assignment repository should be checked out; can be null if not applicable,
* default would be used.
* @param testCheckoutPath The directory within the container where the test repository should be checked out; can be null if not applicable, default
* would be used.
* @param solutionCheckoutPath The directory within the container where the solution repository should be checked out; can be null if not applicable, default
* would be used.
*/
public void populateBuildJobContainer(String buildJobContainerId, Path assignmentRepositoryPath, Path testRepositoryPath, Path solutionRepositoryPath,
Path[] auxiliaryRepositoriesPaths, String[] auxiliaryRepositoryCheckoutDirectories, ProgrammingLanguage programmingLanguage) {
String testCheckoutPath = RepositoryCheckoutPath.TEST.forProgrammingLanguage(programmingLanguage);
String assignmentCheckoutPath = RepositoryCheckoutPath.ASSIGNMENT.forProgrammingLanguage(programmingLanguage);
Path[] auxiliaryRepositoriesPaths, String[] auxiliaryRepositoryCheckoutDirectories, ProgrammingLanguage programmingLanguage, String assignmentCheckoutPath,
String testCheckoutPath, String solutionCheckoutPath) {

assignmentCheckoutPath = (!StringUtils.isBlank(assignmentCheckoutPath)) ? assignmentCheckoutPath
: RepositoryCheckoutPath.ASSIGNMENT.forProgrammingLanguage(programmingLanguage);

BBesrour marked this conversation as resolved.
Show resolved Hide resolved
String defaultTestCheckoutPath = RepositoryCheckoutPath.TEST.forProgrammingLanguage(programmingLanguage);
testCheckoutPath = (!StringUtils.isBlank(defaultTestCheckoutPath) && !StringUtils.isBlank(testCheckoutPath)) ? testCheckoutPath : defaultTestCheckoutPath;

// Make sure to create the working directory in case it does not exist.
// In case the test checkout path is the working directory, we only create up to the parent, as the working directory is created below.
Expand All @@ -292,7 +304,8 @@ public void populateBuildJobContainer(String buildJobContainerId, Path assignmen
// Copy the assignment repository to the container and move it to the assignment checkout path
addAndPrepareDirectory(buildJobContainerId, assignmentRepositoryPath, LOCALCI_WORKING_DIRECTORY + "/testing-dir/" + assignmentCheckoutPath);
if (solutionRepositoryPath != null) {
String solutionCheckoutPath = RepositoryCheckoutPath.SOLUTION.forProgrammingLanguage(programmingLanguage);
solutionCheckoutPath = (!StringUtils.isBlank(solutionCheckoutPath)) ? solutionCheckoutPath
: RepositoryCheckoutPath.SOLUTION.forProgrammingLanguage(programmingLanguage);
addAndPrepareDirectory(buildJobContainerId, solutionRepositoryPath, LOCALCI_WORKING_DIRECTORY + "/testing-dir/" + solutionCheckoutPath);
}
for (int i = 0; i < auxiliaryRepositoriesPaths.length; i++) {
Expand All @@ -309,6 +322,7 @@ private void createScriptFile(String buildJobContainerId) {

private void addAndPrepareDirectory(String containerId, Path repositoryPath, String newDirectoryName) {
copyToContainer(repositoryPath.toString(), containerId);
addDirectory(containerId, getParentFolderPath(newDirectoryName), true);
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
renameDirectoryOrFile(containerId, LOCALCI_WORKING_DIRECTORY + "/" + repositoryPath.getFileName().toString(), newDirectoryName);
}

Expand Down Expand Up @@ -428,4 +442,9 @@ private Container getContainerForName(String containerName) {
List<Container> containers = dockerClient.listContainersCmd().withShowAll(true).exec();
return containers.stream().filter(container -> container.getNames()[0].equals("/" + containerName)).findFirst().orElse(null);
}

private String getParentFolderPath(String path) {
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
Path parentPath = Paths.get(path).normalize().getParent();
return parentPath != null ? parentPath.toString() : "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ private BuildResult runScriptAndParseResults(BuildJobQueueItem buildJob, String
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
log.debug(msg);
buildJobContainerService.populateBuildJobContainer(containerId, assignmentRepositoryPath, testsRepositoryPath, solutionRepositoryPath, auxiliaryRepositoriesPaths,
buildJob.repositoryInfo().auxiliaryRepositoryCheckoutDirectories(), buildJob.buildConfig().programmingLanguage());
buildJob.repositoryInfo().auxiliaryRepositoryCheckoutDirectories(), buildJob.buildConfig().programmingLanguage(), buildJob.buildConfig().assignmentCheckoutPath(),
buildJob.buildConfig().testCheckoutPath(), buildJob.buildConfig().solutionCheckoutPath());

msg = "~~~~~~~~~~~~~~~~~~~~ Executing Build Script for Build job " + buildJob.id() + " ~~~~~~~~~~~~~~~~~~~~";
buildLogsMap.appendBuildLogEntry(buildJob.id(), msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,17 @@ public CompletableFuture<BuildResult> executeBuildJob(BuildJobQueueItem buildJob
lock.unlock();
}

int buildJobTimeoutSeconds;
if (buildJobItem.buildConfig().timeoutSeconds() != 0 && buildJobItem.buildConfig().timeoutSeconds() < this.timeoutSeconds) {
buildJobTimeoutSeconds = buildJobItem.buildConfig().timeoutSeconds();
}
else {
buildJobTimeoutSeconds = this.timeoutSeconds;
}

CompletableFuture<BuildResult> futureResult = createCompletableFuture(() -> {
try {
return future.get(timeoutSeconds, TimeUnit.SECONDS);
return future.get(buildJobTimeoutSeconds, TimeUnit.SECONDS);
}
catch (Exception e) {
// RejectedExecutionException is thrown if the queue size limit (defined in "artemis.continuous-integration.queue-size-limit") is reached.
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ public final class Constants {
// Used to cut off CI specific path segments when receiving static code analysis reports
public static final String ASSIGNMENT_DIRECTORY = "/" + ASSIGNMENT_REPO_NAME + "/";

public static final String TEST_WORKING_DIRECTORY = "test";

// Used as a value for <sourceDirectory> for the Java template pom.xml
public static final String STUDENT_WORKING_DIRECTORY = ASSIGNMENT_DIRECTORY + "src";

Expand Down Expand Up @@ -390,6 +392,18 @@ public final class Constants {
*/
public static final int MIN_SCORE_ORANGE = 40;

public static final String ASSIGNMENT_REPO_PLACEHOLDER = "${studentWorkingDirectory}";

public static final String TEST_REPO_PLACEHOLDER = "${testWorkingDirectory}";

public static final String SOLUTION_REPO_PLACEHOLDER = "${solutionWorkingDirectory}";

public static final String ASSIGNMENT_REPO_PARENT_PLACEHOLDER = "${studentParentWorkingDirectoryName}";

public static final String ASSIGNMENT_REPO_PLACEHOLDER_NO_SLASH = "${studentWorkingDirectoryNoSlash}";

public static final Pattern ALLOWED_CHECKOUT_DIRECTORY = Pattern.compile("[\\w-]+(/[\\w-]+)*$");

private Constants() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ public class ProgrammingExerciseBuildConfig extends DomainObject {
private boolean checkoutSolutionRepository = false;

@Column(name = "checkout_path")
private String checkoutPath;
private String testCheckoutPath;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I think we could also update the column name, but I don't have a strong opinion on that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the entry in the database would make it harder to test on TS since deployed this PR would break the TS for other PRs


@Column(name = "assignment_checkout_path")
private String assignmentCheckoutPath;

@Column(name = "solution_checkout_path")
private String solutionCheckoutPath;

@Column(name = "timeout_seconds")
private int timeoutSeconds;
Expand Down Expand Up @@ -85,7 +91,9 @@ public ProgrammingExerciseBuildConfig() {
public ProgrammingExerciseBuildConfig(ProgrammingExerciseBuildConfig originalBuildConfig) {
this.setBranch(originalBuildConfig.getBranch());
this.setBuildPlanConfiguration(originalBuildConfig.getBuildPlanConfiguration());
this.setCheckoutPath(originalBuildConfig.getCheckoutPath());
this.setTestCheckoutPath(originalBuildConfig.getTestCheckoutPath());
this.setAssignmentCheckoutPath(originalBuildConfig.getAssignmentCheckoutPath());
this.setSolutionCheckoutPath(originalBuildConfig.getSolutionCheckoutPath());
this.setCheckoutSolutionRepository(originalBuildConfig.getCheckoutSolutionRepository());
this.setDockerFlags(originalBuildConfig.getDockerFlags());
this.setSequentialTestRuns(originalBuildConfig.hasSequentialTestRuns());
Expand Down Expand Up @@ -166,12 +174,12 @@ public void setCheckoutSolutionRepository(boolean checkoutSolutionRepository) {
this.checkoutSolutionRepository = checkoutSolutionRepository;
}

public String getCheckoutPath() {
return checkoutPath;
public String getTestCheckoutPath() {
return testCheckoutPath;
}

public void setCheckoutPath(String checkoutPath) {
this.checkoutPath = checkoutPath;
public void setTestCheckoutPath(String testCheckoutPath) {
this.testCheckoutPath = testCheckoutPath;
}

public int getTimeoutSeconds() {
Expand Down Expand Up @@ -268,11 +276,27 @@ public void generateAndSetBuildPlanAccessSecret() {
buildPlanAccessSecret = UUID.randomUUID().toString();
}

public String getAssignmentCheckoutPath() {
return assignmentCheckoutPath;
}

public void setAssignmentCheckoutPath(String assignmentCheckoutPath) {
this.assignmentCheckoutPath = assignmentCheckoutPath;
}

public String getSolutionCheckoutPath() {
return solutionCheckoutPath;
}

public void setSolutionCheckoutPath(String solutionCheckoutPath) {
this.solutionCheckoutPath = solutionCheckoutPath;
}

@Override
public String toString() {
return "BuildJobConfig{" + "id=" + getId() + ", sequentialTestRuns=" + sequentialTestRuns + ", branch='" + branch + '\'' + ", buildPlanConfiguration='"
+ buildPlanConfiguration + '\'' + ", buildScript='" + buildScript + '\'' + ", checkoutSolutionRepository=" + checkoutSolutionRepository + ", checkoutPath='"
+ checkoutPath + '\'' + ", timeoutSeconds=" + timeoutSeconds + ", dockerFlags='" + dockerFlags + '\'' + ", testwiseCoverageEnabled=" + testwiseCoverageEnabled
+ testCheckoutPath + '\'' + ", timeoutSeconds=" + timeoutSeconds + ", dockerFlags='" + dockerFlags + '\'' + ", testwiseCoverageEnabled=" + testwiseCoverageEnabled
+ ", theiaImage='" + theiaImage + '\'' + ", allowBranching=" + allowBranching + ", branchRegex='" + branchRegex + '\'' + '}';
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package de.tum.cit.aet.artemis.programming.service;

import static de.tum.cit.aet.artemis.core.config.Constants.ALLOWED_CHECKOUT_DIRECTORY;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
Expand All @@ -27,8 +27,6 @@ public class AuxiliaryRepositoryService {

private static final String AUX_REPO_ENTITY_NAME = "programmingExercise";

private static final Pattern ALLOWED_CHECKOUT_DIRECTORY = Pattern.compile("[\\w-]+(/[\\w-]+)*$");

private final AuxiliaryRepositoryRepository auxiliaryRepositoryRepository;

public AuxiliaryRepositoryService(AuxiliaryRepositoryRepository auxiliaryRepositoryRepository) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
Expand All @@ -18,6 +19,8 @@
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.core.config.Constants;
import de.tum.cit.aet.artemis.core.service.ProfileService;
import de.tum.cit.aet.artemis.core.service.ResourceLoaderService;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseBuildConfig;
Expand All @@ -39,14 +42,17 @@ public class BuildScriptProviderService {

private final Map<String, String> scriptCache = new ConcurrentHashMap<>();

private final ProfileService profileService;

/**
* Constructor for BuildScriptProvider, which loads all scripts into the cache to speed up retrieval
* during the runtime of the application
*
* @param resourceLoaderService resourceLoaderService
*/
public BuildScriptProviderService(ResourceLoaderService resourceLoaderService) {
public BuildScriptProviderService(ResourceLoaderService resourceLoaderService, ProfileService profileService) {
this.resourceLoaderService = resourceLoaderService;
this.profileService = profileService;
}

/**
Expand All @@ -69,6 +75,9 @@ public void cacheOnBoot() {
String uniqueKey = directory + "_" + filename;
byte[] fileContent = IOUtils.toByteArray(resource.getInputStream());
String script = new String(fileContent, StandardCharsets.UTF_8);
if (!profileService.isLocalCiActive()) {
script = replacePlaceholders(script, null, null, null);
}
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
scriptCache.put(uniqueKey, script);
}
catch (IOException e) {
Expand Down Expand Up @@ -112,6 +121,9 @@ public String getScriptFor(ProgrammingLanguage programmingLanguage, Optional<Pro
}
byte[] fileContent = IOUtils.toByteArray(fileResource.getInputStream());
String script = new String(fileContent, StandardCharsets.UTF_8);
if (!profileService.isLocalCiActive()) {
script = replacePlaceholders(script, null, null, null);
}
scriptCache.put(uniqueKey, script);
log.debug("Caching script for {}", uniqueKey);
return script;
Expand Down Expand Up @@ -166,4 +178,42 @@ public String buildTemplateName(Optional<ProjectType> projectType, Boolean stati
}
return String.join("_", fileNameComponents) + "." + fileExtension;
}

/**
* Replaces placeholders in the given result paths with the actual paths.
*
* @param resultPaths the result paths to replace the placeholders in
* @param buildConfig the build configuration containing the actual paths
* @return the result paths with the placeholders replaced
*/
public List<String> replaceResultPathsPlaceholders(List<String> resultPaths, ProgrammingExerciseBuildConfig buildConfig) {
List<String> replacedResultPaths = new ArrayList<>();
for (String resultPath : resultPaths) {
String replacedResultPath = replacePlaceholders(resultPath, buildConfig.getAssignmentCheckoutPath(), buildConfig.getSolutionCheckoutPath(),
buildConfig.getTestCheckoutPath());
replacedResultPaths.add(replacedResultPath);
}
return replacedResultPaths;
}

/**
* Replaces placeholders in the given original string with the actual paths.
*
* @param originalString the original string to replace the placeholders in
* @param assignmentRepo the assignment repository name
* @param solutionRepo the solution repository name
* @param testRepo the test repository name
* @return the original string with the placeholders replaced
*/
public String replacePlaceholders(String originalString, String assignmentRepo, String solutionRepo, String testRepo) {
assignmentRepo = !StringUtils.isBlank(assignmentRepo) ? assignmentRepo : Constants.ASSIGNMENT_REPO_NAME;
solutionRepo = solutionRepo != null && !solutionRepo.isBlank() ? solutionRepo : Constants.SOLUTION_REPO_NAME;
testRepo = testRepo != null && !testRepo.isBlank() ? testRepo : Constants.TEST_REPO_NAME;
BBesrour marked this conversation as resolved.
Show resolved Hide resolved

String replacedResultPath = originalString.replace(Constants.ASSIGNMENT_REPO_PARENT_PLACEHOLDER, assignmentRepo);
replacedResultPath = replacedResultPath.replace(Constants.ASSIGNMENT_REPO_PLACEHOLDER, "/" + assignmentRepo + "/src");
replacedResultPath = replacedResultPath.replace(Constants.SOLUTION_REPO_PLACEHOLDER, solutionRepo);
replacedResultPath = replacedResultPath.replace(Constants.TEST_REPO_PLACEHOLDER, testRepo);
return replacedResultPath;
}
BBesrour marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading