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

Added maximum string length constraint. (#449) #452

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -35,6 +35,10 @@
return isValidInteger(value);
}

public FormValidation doMaxStringLengthConstraint(@QueryParameter String value) {
return isValidInteger(value);

Check warning on line 39 in src/main/java/net/masterthought/jenkins/CucumberReportDescriptor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 39 is not covered by tests
}

public FormValidation doCheckFailedStepsNumber(@QueryParameter String value) {
return isValidInteger(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Set;
import java.util.UUID;

import com.fasterxml.jackson.core.StreamReadConstraints;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.AbortException;
import hudson.Extension;
Expand Down Expand Up @@ -78,487 +79,499 @@
private boolean undefinedAsNotFailingStatus;

private int trendsLimit;
private int maxStringLengthConstraint = 20_000_000;
Copy link
Member

Choose a reason for hiding this comment

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

nice,
would be perfect having reference to the code from that value was taken

Copy link
Author

Choose a reason for hiding this comment

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

private String sortingMethod;
private List<Classification> classifications;
private String customJsFiles;
private String customCssFiles;

private boolean mergeFeaturesById;
private boolean mergeFeaturesWithRetest;
private boolean hideEmptyHooks;
private boolean skipEmptyJSONFiles;
private boolean expandAllSteps;

private String classificationsFilePattern = "";

@DataBoundConstructor
public CucumberReportPublisher(String fileIncludePattern) {
this.fileIncludePattern = fileIncludePattern;
}

/**
* This method, invoked after object is resurrected from persistence,
* to keep backward compatibility.
*/
protected void keepBackwardCompatibility() {
if (classifications == null) {
classifications = new ArrayList<>();
}
if (sortingMethod == null) {
sortingMethod = SortingMethod.NATURAL.name();
}

reportTitle = StringUtils.defaultString(reportTitle);
}

private static void log(TaskListener listener, String message) {
listener.getLogger().println("[CucumberReport] " + message);
}

public String getFileIncludePattern() {
return fileIncludePattern;
}

public List<Classification> getClassifications() {
return classifications;
}

@DataBoundSetter
public void setClassifications(List<Classification> classifications) {
// don't store the classifications if there was no element provided
if (CollectionUtils.isNotEmpty(classifications)) {
this.classifications = classifications;
}
}

@DataBoundSetter
public void setCustomJsFiles(String customJsFiles) {
this.customJsFiles = customJsFiles;
}

public String getCustomJsFiles() {
return customJsFiles;
}

@DataBoundSetter
public void setCustomCssFiles(String customCssFiles) {
this.customCssFiles = customCssFiles;
}

public String getCustomCssFiles() {
return customCssFiles;
}

public int getTrendsLimit() {
return trendsLimit;
}

@DataBoundSetter
public void setTrendsLimit(int trendsLimit) {
this.trendsLimit = trendsLimit;
}

public int getMaxStringLengthConstraint() {
return maxStringLengthConstraint;
}

@DataBoundSetter
public void setMaxStringLengthConstraint(int maxStringLengthConstraint) {
this.maxStringLengthConstraint = maxStringLengthConstraint;
}

public String getFileExcludePattern() {
return fileExcludePattern;
}

@DataBoundSetter
public void setFileExcludePattern(String fileExcludePattern) {
this.fileExcludePattern = fileExcludePattern;
}

public String getJsonReportDirectory() {
return jsonReportDirectory;
}

@DataBoundSetter
public void setJsonReportDirectory(String jsonReportDirectory) {
this.jsonReportDirectory = jsonReportDirectory;
}

public String getReportTitle() {
return reportTitle;
}

@DataBoundSetter
public void setReportTitle(String reportTitle) {
this.reportTitle = StringUtils.isEmpty(reportTitle) ? "" : reportTitle.trim();
}

public String getDirectorySuffix() {
return StringUtils.isEmpty(this.reportTitle)
? ""
: UUID.nameUUIDFromBytes(reportTitle.getBytes(StandardCharsets.UTF_8)).toString();
}

public String getDirectorySuffixWithSeparator() {
return StringUtils.isEmpty(this.reportTitle)
? ""
: ReportBuilder.SUFFIX_SEPARATOR + getDirectorySuffix();
}

public int getFailedStepsNumber() {
return failedStepsNumber;
}

@DataBoundSetter
public void setFailedStepsNumber(int failedStepsNumber) {
this.failedStepsNumber = failedStepsNumber;
}

public int getSkippedStepsNumber() {
return skippedStepsNumber;
}

@DataBoundSetter
public void setSkippedStepsNumber(int skippedStepsNumber) {
this.skippedStepsNumber = skippedStepsNumber;
}

public int getPendingStepsNumber() {
return pendingStepsNumber;
}

@DataBoundSetter
public void setPendingStepsNumber(int pendingStepsNumber) {
this.pendingStepsNumber = pendingStepsNumber;
}

public int getUndefinedStepsNumber() {
return undefinedStepsNumber;
}

@DataBoundSetter
public void setUndefinedStepsNumber(int undefinedStepsNumber) {
this.undefinedStepsNumber = undefinedStepsNumber;
}

public int getFailedScenariosNumber() {
return failedScenariosNumber;
}

@DataBoundSetter
public void setFailedScenariosNumber(int failedScenariosNumber) {
this.failedScenariosNumber = failedScenariosNumber;
}

public int getFailedFeaturesNumber() {
return failedFeaturesNumber;
}

@DataBoundSetter
public void setFailedFeaturesNumber(int failedFeaturesNumber) {
this.failedFeaturesNumber = failedFeaturesNumber;
}


public double getFailedStepsPercentage() {
return failedStepsPercentage;
}

@DataBoundSetter
public void setFailedStepsPercentage(double failedStepsPercentage) {
this.failedStepsPercentage = failedStepsPercentage;
}

public double getSkippedStepsPercentage() {
return skippedStepsPercentage;
}

@DataBoundSetter
public void setSkippedStepsPercentage(double skippedStepsPercentage) {
this.skippedStepsPercentage = skippedStepsPercentage;
}

public double getPendingStepsPercentage() {
return pendingStepsPercentage;
}

@DataBoundSetter
public void setPendingStepsPercentage(double pendingStepsPercentage) {
this.pendingStepsPercentage = pendingStepsPercentage;
}

public double getUndefinedStepsPercentage() {
return undefinedStepsPercentage;
}

@DataBoundSetter
public void setUndefinedStepsPercentage(double undefinedStepsPercentage) {
this.undefinedStepsPercentage = undefinedStepsPercentage;
}

public double getFailedScenariosPercentage() {
return failedScenariosPercentage;
}

@DataBoundSetter
public void setFailedScenariosPercentage(double failedScenariosPercentage) {
this.failedScenariosPercentage = failedScenariosPercentage;
}

public double getFailedFeaturesPercentage() {
return failedFeaturesPercentage;
}

@DataBoundSetter
public void setFailedFeaturesPercentage(double failedFeaturesPercentage) {
this.failedFeaturesPercentage = failedFeaturesPercentage;
}


public String getBuildStatus() {
return buildStatus;
}

@DataBoundSetter
public void setBuildStatus(String buildStatus) {
this.buildStatus = buildStatus;
}

@DataBoundSetter
public void setStopBuildOnFailedReport(boolean stopBuildOnFailedReport) {
this.stopBuildOnFailedReport = stopBuildOnFailedReport;
}

public boolean getStopBuildOnFailedReport() {
return stopBuildOnFailedReport;
}

@DataBoundSetter
public void setFailedAsNotFailingStatus(boolean failedAsNotFailingStatus) {
this.failedAsNotFailingStatus = failedAsNotFailingStatus;
}

public boolean getFailedAsNotFailingStatus() {
return failedAsNotFailingStatus;
}

@DataBoundSetter
public void setSkippedAsNotFailingStatus(boolean skippedAsNotFailingStatus) {
this.skippedAsNotFailingStatus = skippedAsNotFailingStatus;
}

public boolean getSkippedAsNotFailingStatus() {
return skippedAsNotFailingStatus;
}

@DataBoundSetter
public void setPendingAsNotFailingStatus(boolean pendingAsNotFailingStatus) {
this.pendingAsNotFailingStatus = pendingAsNotFailingStatus;
}

public boolean getPendingAsNotFailingStatus() {
return pendingAsNotFailingStatus;
}

@DataBoundSetter
public void setUndefinedAsNotFailingStatus(boolean undefinedAsNotFailingStatus) {
this.undefinedAsNotFailingStatus = undefinedAsNotFailingStatus;
}

public boolean getUndefinedAsNotFailingStatus() {
return undefinedAsNotFailingStatus;
}


@DataBoundSetter
public void setSortingMethod(String sortingMethod) {
this.sortingMethod = sortingMethod;
}

public String getSortingMethod() {
return sortingMethod;
}

@DataBoundSetter
public void setClassificationsFilePattern(String classificationsFilePattern) {
this.classificationsFilePattern = classificationsFilePattern;
}

public String getClassificationsFilePattern() {
return classificationsFilePattern;
}

@DataBoundSetter
public void setMergeFeaturesById(boolean mergeFeaturesById) {
this.mergeFeaturesById = mergeFeaturesById;
}

public boolean getMergeFeaturesById() {
return mergeFeaturesById;
}

@DataBoundSetter
public void setMergeFeaturesWithRetest(boolean mergeFeaturesWithRetest) {
this.mergeFeaturesWithRetest = mergeFeaturesWithRetest;
}

public boolean getMergeFeaturesWithRetest() {
return mergeFeaturesWithRetest;
}

@DataBoundSetter
public void setHideEmptyHooks(boolean hideEmptyHooks) {
this.hideEmptyHooks = hideEmptyHooks;
}

public boolean getHideEmptyHooks() {
return hideEmptyHooks;
}

@DataBoundSetter
public void setSkipEmptyJSONFiles(boolean skipEmptyJSONFiles) {
this.skipEmptyJSONFiles = skipEmptyJSONFiles;
}

public boolean getSkipEmptyJSONFiles() {
return skipEmptyJSONFiles;
}

@DataBoundSetter
public void setExpandAllSteps(boolean expandAllSteps) {
this.expandAllSteps = expandAllSteps;
}

public boolean getExpandAllSteps() {
return expandAllSteps;
}

@Override
public void perform(@NonNull Run<?, ?> run, @NonNull FilePath workspace, @NonNull Launcher launcher, @NonNull TaskListener listener)
throws InterruptedException, IOException {

keepBackwardCompatibility();

generateReport(run, workspace, listener);

SafeArchiveServingRunAction caa = new SafeArchiveServingRunAction(
run,
new File(run.getRootDir(), ReportBuilder.BASE_DIRECTORY + getDirectorySuffixWithSeparator()),
ReportBuilder.BASE_DIRECTORY + getDirectorySuffixWithSeparator(),
ReportBuilder.HOME_PAGE,
CucumberReportBaseAction.ICON_NAME,
getActionName(),
getDirectorySuffixWithSeparator()
);
run.addAction(caa);
}

private String getActionName() {
return StringUtils.isEmpty(reportTitle) ? Messages.SidePanel_DisplayNameNoTitle() : String.format(Messages.SidePanel_DisplayName(), reportTitle);
}

private void generateReport(Run<?, ?> build, FilePath workspace, TaskListener listener) throws InterruptedException, IOException {

log(listener, "Using Cucumber Reports version " + getPomVersion(listener));

// create directory where trends will be stored
final File trendsDir = new File(build.getParent().getRootDir(), TRENDS_DIR + getDirectorySuffixWithSeparator());
if (!trendsDir.exists() && !trendsDir.mkdirs()) {
throw new IllegalStateException("Could not create directory for trends: " + trendsDir);
}

// source directory (possibly on slave)
String parsedJsonReportDirectory = evaluateMacro(build, workspace, listener, jsonReportDirectory);
log(listener, String.format("JSON report directory is \"%s\"", parsedJsonReportDirectory));
FilePath inputReportDirectory = new FilePath(workspace, parsedJsonReportDirectory);

File directoryForReport = build.getRootDir();
File directoryCache = new File(
directoryForReport,
ReportBuilder.BASE_DIRECTORY + getDirectorySuffixWithSeparator() + File.separatorChar + ".cache"
);

if (directoryCache.exists()) {
throw new IllegalStateException("Cache directory " + directoryCache + " already exists. Another report with the same title already generated?");
} else if (!directoryCache.mkdirs()) {
throw new IllegalStateException("Could not create directory for cache: " + directoryCache);
}

// copies Classifications files to cache...
int copiedFilesProperties = inputReportDirectory.copyRecursiveTo(DEFAULT_FILE_INCLUDE_PATTERN_CLASSIFICATIONS, new FilePath(directoryCache));
log(listener, String.format("Copied %d properties files from workspace \"%s\" to reports directory \"%s\"",
copiedFilesProperties, inputReportDirectory.getRemote(), directoryCache));

// copies custom JS and CSS files to cache...
List<String> cachedCustomJsFiles = new ArrayList<>();
if (StringUtils.isNotEmpty(customJsFiles)) {
cachedCustomJsFiles.addAll(copyFilesAndGetList(listener,
workspace,
directoryCache,
customJsFiles,
null)
);
}
List<String> cachedCustomCssFiles = new ArrayList<>();
if (StringUtils.isNotEmpty(customCssFiles)) {
cachedCustomCssFiles.addAll(copyFilesAndGetList(listener,
workspace,
directoryCache,
customCssFiles,
null)
);
}

// copies JSON files to cache and
// exclude JSONs that should be skipped (as configured by the user)
List<String> jsonFilesToProcess = copyFilesAndGetList(listener, inputReportDirectory, directoryCache, fileIncludePattern, fileExcludePattern);
log(listener, String.format("Processing %d json files:", jsonFilesToProcess.size()));
for (String jsonFile : jsonFilesToProcess) {
log(listener, jsonFile);
}

String buildNumber = Integer.toString(build.getNumber());
// this works for normal and multi-config/matrix jobs
// for matrix jobs, this will include the matrix job name and the specific
// configuration/permutation name as well. this also includes the '/' so
// we don't have to modify how the cucumber plugin report generator's links
String projectName = build.getParent().getDisplayName();

Configuration configuration = new Configuration(directoryForReport, projectName);
configuration.setBuildNumber(buildNumber);
configuration.setDirectorySuffix(getDirectorySuffix());
configuration.setTrends(new File(trendsDir, TRENDS_FILE), trendsLimit);
configuration.setSortingMethod(SortingMethod.valueOf(sortingMethod));
if (mergeFeaturesById) {
configuration.addReducingMethod(ReducingMethod.MERGE_FEATURES_BY_ID);
}
if (mergeFeaturesWithRetest) {
configuration.addReducingMethod(ReducingMethod.MERGE_FEATURES_WITH_RETEST);
}
if (skipEmptyJSONFiles) {
configuration.addReducingMethod(ReducingMethod.SKIP_EMPTY_JSON_FILES);
}
if (hideEmptyHooks) {
configuration.addReducingMethod(ReducingMethod.HIDE_EMPTY_HOOKS);
}
if (expandAllSteps) {
configuration.addPresentationModes(PresentationMode.EXPAND_ALL_STEPS);
}

configuration.addPresentationModes(PresentationMode.RUN_WITH_JENKINS);

if (CollectionUtils.isNotEmpty(classifications)) {
log(listener, String.format("Adding %d classification(s)", classifications.size()));
addClassificationsToBuildReport(build, workspace, listener, configuration, classifications);
}

if (CollectionUtils.isNotEmpty(cachedCustomJsFiles)) {
configuration.addCustomJsFiles(cachedCustomJsFiles);
}

if (CollectionUtils.isNotEmpty(cachedCustomCssFiles)) {
configuration.addCustomCssFiles(cachedCustomCssFiles);
}

List<String> classificationFiles = fetchPropertyFiles(directoryCache, listener);
if (CollectionUtils.isNotEmpty(classificationFiles)) {
configuration.addClassificationFiles(classificationFiles);
}

setFailingStatuses(configuration);

StreamReadConstraints.overrideDefaultStreamReadConstraints(StreamReadConstraints.builder().maxStringLength(maxStringLengthConstraint).build());

Check warning on line 573 in src/main/java/net/masterthought/jenkins/CucumberReportPublisher.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 82-573 are not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

that should be implemented in core component https://github.com/damianszczepanik/cucumber-reporting

Copy link
Author

Choose a reason for hiding this comment

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

On it.


ReportBuilder reportBuilder = new ReportBuilder(jsonFilesToProcess, configuration);
Reportable result = reportBuilder.generateReports();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
field="trendsLimit">
<f:textbox default="0"/>
</f:entry>
<f:entry
title="${%maxStringLengthConstraint.title}"
field="maxStringLengthConstraint">
<f:textbox default="20000000"/>
Copy link

@felipecrs felipecrs Feb 21, 2024

Choose a reason for hiding this comment

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

Perhaps we should consider bumping it a little by default. Maybe 25M? This would cover the needs of all the people at #449.

What do you think, @damianszczepanik?

In such case, maybe making it configurable isn't even required (unless someone makes a case where a higher constraint is needed).

Copy link

Choose a reason for hiding this comment

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

@damianszczepanik , would you consider this simple fix from @felipecrs for #449?

Choose a reason for hiding this comment

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

@damianszczepanik, if you agree, I can send a PR for it.

Copy link
Member

Choose a reason for hiding this comment

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

IMO solution should be implemented first in https://github.com/damianszczepanik/cucumber-reporting

</f:entry>
</f:section>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fileIncludePattern.title=File Include Pattern
classificationsFilePattern.title=Classifications File Pattern
fileExcludePattern.title=File Exclude Pattern
trendsLimit.title=Limit for trends
maxStringLengthConstraint.title=Maximum string size (Stream read constraint of JacksonCore)
# ===
buildResult=Build Result
buildResult.description=This section allows to configure when the build is marked as failed or unstable. Result is changed when any of below rule is enabled.
Expand Down