mirror of https://github.com/jenkinsci/jenkins.git
Merge branch 'master' into migrate_to_junit5_core_2
This commit is contained in:
commit
ea1e0b9568
|
@ -41,7 +41,7 @@ THE SOFTWARE.
|
|||
<commons-fileupload2.version>2.0.0-M2</commons-fileupload2.version>
|
||||
<groovy.version>2.4.21</groovy.version>
|
||||
<jelly.version>1.1-jenkins-20250108</jelly.version>
|
||||
<stapler.version>1979.v5048d87384d7</stapler.version>
|
||||
<stapler.version>1983.v93c53e94b_c04</stapler.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
|
|
@ -135,6 +135,7 @@ import net.sf.json.JSONObject;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.jelly.XMLOutput;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.DoNotUse;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.QueryParameter;
|
||||
|
@ -1073,6 +1074,11 @@ public abstract class Run<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
|
|||
}
|
||||
}
|
||||
|
||||
@Restricted(DoNotUse.class) // Jelly
|
||||
public final boolean hasCustomArtifactManager() {
|
||||
return artifactManager != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory where the artifacts are archived.
|
||||
* @deprecated Should only be used from {@link StandardArtifactManager} or subclasses.
|
||||
|
|
|
@ -43,6 +43,7 @@ import java.util.Map;
|
|||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.SortedMap;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jenkins.model.lazy.AbstractLazyLoadRunMap;
|
||||
|
@ -240,6 +241,7 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
|
|||
return r.createReference();
|
||||
}
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
@Override
|
||||
protected boolean allowLoad(int buildNumber) {
|
||||
if (job == null) {
|
||||
|
@ -256,6 +258,27 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
|
|||
return true;
|
||||
}
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
@Override
|
||||
protected IntPredicate createLoadAllower() {
|
||||
if (job == null) {
|
||||
LOGGER.fine(() -> "deprecated constructor without Job used on " + dir);
|
||||
return buildNumber -> true;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
var allowers = RunListener.all().stream().map(l -> l.createLoadAllower(job)).toList();
|
||||
return buildNumber -> {
|
||||
for (var allower : allowers) {
|
||||
if (!allower.test(buildNumber)) {
|
||||
LOGGER.finer(() -> allower + " declined to load " + buildNumber + " in " + job);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
LOGGER.finest(() -> "no RunListener declined to load " + buildNumber + " in " + job + " so proceeding");
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected R retrieve(File d) throws IOException {
|
||||
if (new File(d, "build.xml").exists()) {
|
||||
|
|
|
@ -47,6 +47,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.IntPredicate;
|
||||
import jenkins.model.lazy.AbstractLazyLoadRunMap;
|
||||
import jenkins.util.Listeners;
|
||||
import org.jvnet.tiger_types.Types;
|
||||
|
@ -187,6 +188,16 @@ public abstract class RunListener<R extends Run> implements ExtensionPoint {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows listeners to veto build loading during startup.
|
||||
* Same behavior as {@link #allowLoad} but permits an implementation to more
|
||||
* efficiently respond to numerous queries about historical build numbers.
|
||||
*/
|
||||
@Restricted(Beta.class)
|
||||
public IntPredicate createLoadAllower(@NonNull Job<?, ?> job) {
|
||||
return buildNumber -> allowLoad(job, buildNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this object as an active listener so that it can start getting
|
||||
* callbacks invoked.
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
package hudson.security;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.model.User;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
|
@ -181,6 +182,7 @@ public class BasicAuthenticationFilter implements CompatibleFilter {
|
|||
d.include(req, rsp);
|
||||
}
|
||||
|
||||
@SuppressFBWarnings(value = "UNVALIDATED_REDIRECT", justification = "Authenticated by the container")
|
||||
private void prepareRedirect(HttpServletResponse rsp, String path) {
|
||||
rsp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
|
||||
rsp.setHeader("Location", path);
|
||||
|
|
|
@ -939,6 +939,9 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
|
|||
return super.encode(rawPassword);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
if (ex.getMessage().equals("password cannot be more than 72 bytes")) {
|
||||
if (rawPassword.toString().matches("\\A\\p{ASCII}+\\z")) {
|
||||
throw new IllegalArgumentException(Messages.HudsonPrivateSecurityRealm_CreateAccount_BCrypt_PasswordTooLong_ASCII());
|
||||
}
|
||||
throw new IllegalArgumentException(Messages.HudsonPrivateSecurityRealm_CreateAccount_BCrypt_PasswordTooLong());
|
||||
}
|
||||
throw ex;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
package hudson.security;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.DescriptorExtensionList;
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionPoint;
|
||||
|
@ -382,6 +383,7 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten
|
|||
rsp.sendRedirect2(getPostLogOutUrl2(req, auth));
|
||||
}
|
||||
|
||||
@SuppressFBWarnings(value = "INSECURE_COOKIE", justification = "TODO needs triage")
|
||||
private void resetRememberMeCookie(StaplerRequest2 req, StaplerResponse2 rsp, String contextPath) {
|
||||
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, "");
|
||||
cookie.setMaxAge(0);
|
||||
|
|
|
@ -72,8 +72,12 @@ public class RenderOnDemandClosure {
|
|||
assert !bodyStack.isEmpty(); // there must be at least one, which is the direct child of <l:renderOnDemand>
|
||||
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
for (String v : Util.fixNull(attributesToCapture).split(","))
|
||||
variables.put(v.intern(), context.getVariable(v));
|
||||
String _attributesToCapture = Util.fixEmpty(attributesToCapture);
|
||||
if (_attributesToCapture != null) {
|
||||
for (String v : _attributesToCapture.split(",")) {
|
||||
variables.put(v.intern(), context.getVariable(v));
|
||||
}
|
||||
}
|
||||
|
||||
// capture the current base of context for descriptors
|
||||
currentDescriptorByNameUrl = Descriptor.getCurrentDescriptorByNameUrl();
|
||||
|
@ -86,6 +90,7 @@ public class RenderOnDemandClosure {
|
|||
for (String adjunct : _adjuncts) {
|
||||
this.adjuncts[i++] = adjunct.intern();
|
||||
}
|
||||
LOGGER.fine(() -> "captured " + variables);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,6 +101,7 @@ public class RenderOnDemandClosure {
|
|||
return new HttpResponse() {
|
||||
@Override
|
||||
public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object node) throws IOException, ServletException {
|
||||
LOGGER.fine(() -> "rendering " + req.getPathInfo());
|
||||
req.getWebApp().getDispatchValidator().allowDispatch(req, rsp);
|
||||
try {
|
||||
new DefaultScriptInvoker() {
|
||||
|
|
|
@ -761,6 +761,7 @@ public class SetupWizard extends PageDecorator {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressFBWarnings(value = "UNVALIDATED_REDIRECT", justification = "TODO needs triage")
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
// Force root requests to the setup wizard
|
||||
if (request instanceof HttpServletRequest && !Jenkins.get().getInstallState().isSetupComplete()) {
|
||||
|
|
|
@ -4964,6 +4964,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
|||
/**
|
||||
* Changes the icon size by changing the cookie
|
||||
*/
|
||||
@SuppressFBWarnings(value = "INSECURE_COOKIE", justification = "TODO needs triage")
|
||||
public void doIconSize(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException, ServletException {
|
||||
String qs = req.getQueryString();
|
||||
if (qs == null)
|
||||
|
|
|
@ -50,6 +50,7 @@ import java.util.Spliterator;
|
|||
import java.util.Spliterators;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -343,6 +344,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
kids = MemoryReductionUtil.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
SortedIntList list = new SortedIntList(kids.length / 2);
|
||||
var allower = createLoadAllower();
|
||||
for (String s : kids) {
|
||||
if (!BUILD_NUMBER.matcher(s).matches()) {
|
||||
// not a build directory
|
||||
|
@ -350,7 +352,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
}
|
||||
try {
|
||||
int buildNumber = Integer.parseInt(s);
|
||||
if (allowLoad(buildNumber)) {
|
||||
if (allower.test(buildNumber)) {
|
||||
list.add(buildNumber);
|
||||
} else {
|
||||
LOGGER.fine(() -> "declining to consider " + buildNumber + " in " + dir);
|
||||
|
@ -368,6 +370,11 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
return true;
|
||||
}
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
protected IntPredicate createLoadAllower() {
|
||||
return this::allowLoad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permits a previous blocked build number to be eligible for loading.
|
||||
* @param buildNumber a build number
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
new hudson.util.IOException2($t) :: $t instanceof Throwable => new java.io.IOException($t);;
|
||||
new hudson.util.IOException2($s, $t) :: $s instanceof String && $t instanceof Throwable => new java.io.IOException($s, $t);;
|
|
@ -38,9 +38,7 @@ THE SOFTWARE.
|
|||
</t:summary>
|
||||
</j:forEach>
|
||||
|
||||
<t:artifactList caption="${%Last Successful Artifacts}"
|
||||
build="${it.lastSuccessfulBuild}" baseURL="lastSuccessfulBuild/"
|
||||
permission="${it.lastSuccessfulBuild.ARTIFACTS}"/>
|
||||
<t:artifactList build="${it.lastSuccessfulBuild}" caption="${%Last Successful Artifacts}"/>
|
||||
</table>
|
||||
|
||||
<!-- merge fragments from the actions -->
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
The MIT License
|
||||
|
||||
Copyright 2025 CloudBees, Inc.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:t="/lib/hudson">
|
||||
<l:ajax>
|
||||
<j:if test="${!h.artifactsPermissionEnabled or h.artifactsPermissionEnabled and it.hasPermission(it.ARTIFACTS)}">
|
||||
<j:set var="artifacts" value="${it.getArtifactsUpTo(it.LIST_CUTOFF+1)}" />
|
||||
<j:if test="${!empty(artifacts)}">
|
||||
<t:summary icon="symbol-cube" href="${rootURL}/${it.url}artifact/">
|
||||
<a href="${rootURL}/${it.url}artifact/">${caption != null ? caption : request2.getParameter('caption')}</a>
|
||||
<j:if test="${it.building}">
|
||||
<p>
|
||||
${%building}
|
||||
</p>
|
||||
</j:if>
|
||||
<j:if test="${size(artifacts) le it.LIST_CUTOFF}">
|
||||
<!-- if not too many, just list them -->
|
||||
<table class="fileList">
|
||||
<j:forEach var="f" items="${artifacts}">
|
||||
<tr>
|
||||
<td>
|
||||
<l:icon class="icon-document icon-sm"/>
|
||||
</td>
|
||||
<td>
|
||||
<a href="${rootURL}/${it.url}artifact/${f.href}">${f.displayPath}</a>
|
||||
</td>
|
||||
<td class="fileSize">
|
||||
${h.humanReadableByteSize(f.getFileSize())}
|
||||
</td>
|
||||
<td>
|
||||
<j:if test="${app.fingerprintMap.ready}">
|
||||
<a href="${rootURL}/${it.url}artifact/${f.href}/*fingerprint*/">
|
||||
<l:icon class="icon-fingerprint icon-sm"/>
|
||||
</a>
|
||||
<st:nbsp/>
|
||||
</j:if>
|
||||
<a href="${rootURL}/${it.url}artifact/${f.href}/*view*/">${%view}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</j:forEach>
|
||||
</table>
|
||||
</j:if>
|
||||
<!-- otherwise just show link to directory browser -->
|
||||
</t:summary>
|
||||
</j:if>
|
||||
</j:if>
|
||||
</l:ajax>
|
||||
</j:jelly>
|
|
@ -67,8 +67,7 @@ THE SOFTWARE.
|
|||
|
||||
|
||||
<table>
|
||||
<t:artifactList build="${it}" caption="${%Build Artifacts}"
|
||||
permission="${it.ARTIFACTS}" />
|
||||
<t:artifactList build="${it}" caption="${%Build Artifacts}"/>
|
||||
|
||||
<!-- give actions a chance to contribute summary item -->
|
||||
<j:forEach var="a" items="${it.allActions}">
|
||||
|
|
|
@ -72,7 +72,7 @@ THE SOFTWARE.
|
|||
<l:card title="Summary">
|
||||
<div>
|
||||
<table>
|
||||
<t:artifactList build="${it}" caption="${%Build Artifacts}" permission="${it.ARTIFACTS}" />
|
||||
<t:artifactList build="${it}" caption="${%Build Artifacts}"/>
|
||||
|
||||
<!-- give actions a chance to contribute summary item -->
|
||||
<j:forEach var="a" items="${it.allActions}">
|
||||
|
|
|
@ -37,7 +37,8 @@ HudsonPrivateSecurityRealm.ManageUserLinks.Description=Create/delete/modify user
|
|||
HudsonPrivateSecurityRealm.CreateAccount.TextNotMatchWordInImage=Text didn''t match the word shown in the image
|
||||
HudsonPrivateSecurityRealm.CreateAccount.PasswordNotMatch=Password didn''t match
|
||||
HudsonPrivateSecurityRealm.CreateAccount.FIPS.PasswordLengthInvalid=Password must be at least 14 characters long
|
||||
HudsonPrivateSecurityRealm.CreateAccount.BCrypt.PasswordTooLong=Jenkins’ own user database currently only supports passwords of up to 72 bytes UTF-8 (72 basic ASCII characters, 24-36 CJK characters, or 18 emoji). Please use a shorter password.
|
||||
HudsonPrivateSecurityRealm.CreateAccount.BCrypt.PasswordTooLong.ASCII=Password cannot be longer than 72 characters.
|
||||
HudsonPrivateSecurityRealm.CreateAccount.BCrypt.PasswordTooLong=Password cannot be longer than 72 characters (a-z, A-Z, 0-9, and basic punctuation; fewer when using other characters, like Chinese characters or emoji).
|
||||
HudsonPrivateSecurityRealm.CreateAccount.PasswordRequired=Password is required
|
||||
HudsonPrivateSecurityRealm.CreateAccount.UserNameRequired=User name is required
|
||||
HudsonPrivateSecurityRealm.CreateAccount.UserNameInvalidCharacters=User name must only contain alphanumeric characters, underscore and dash
|
||||
|
|
|
@ -28,7 +28,7 @@ THE SOFTWARE.
|
|||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
|
||||
<l:layout permission="${app.ADMINISTER}" title="${%threadDump}" type="one-column">
|
||||
<f:breadcrumb-config-outline title="${%threadDump}" />
|
||||
<l:breadcrumb title="${%threadDump}" />
|
||||
<l:main-panel>
|
||||
<h1>${%threadDumpTitle}</h1>
|
||||
<j:set var="tdumps" value="${it.getAllThreadDumps()}"/>
|
||||
|
|
|
@ -23,60 +23,28 @@ THE SOFTWARE.
|
|||
-->
|
||||
|
||||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
|
||||
<st:documentation>
|
||||
Generates a listing of the build artifacts.
|
||||
Depending on the size of the artifact, this will either produce a list or a link to the directory view.
|
||||
|
||||
<st:attribute name="build" type="hudson.model.Build" use="required">
|
||||
<st:attribute name="build" type="hudson.model.Run" use="required">
|
||||
Build object for which the artifacts are displayed
|
||||
</st:attribute>
|
||||
<st:attribute name="caption" use="required">
|
||||
Human readable title text
|
||||
</st:attribute>
|
||||
<st:attribute name="baseURL">
|
||||
If the hyperlink to artifacts are at another URL, specify the prefix.
|
||||
</st:attribute>
|
||||
</st:documentation>
|
||||
<j:if test="${!h.isArtifactsPermissionEnabled() or h.isArtifactsPermissionEnabled() and h.hasPermission(it,attrs.permission)}">
|
||||
<j:set var="artifacts" value="${build.getArtifactsUpTo(build.LIST_CUTOFF+1)}" />
|
||||
<j:if test="${!empty(artifacts)}">
|
||||
<t:summary icon="symbol-cube" href="${baseURL}artifact/">
|
||||
<a href="${baseURL}artifact/">${caption}</a>
|
||||
<j:if test="${build.building}">
|
||||
<p>
|
||||
${%building}
|
||||
</p>
|
||||
</j:if>
|
||||
<j:if test="${size(artifacts) le build.LIST_CUTOFF}">
|
||||
<!-- if not too many, just list them -->
|
||||
<table class="fileList">
|
||||
<j:forEach var="f" items="${artifacts}">
|
||||
<tr>
|
||||
<td>
|
||||
<l:icon class="icon-document icon-sm"/>
|
||||
</td>
|
||||
<td>
|
||||
<a href="${baseURL}artifact/${f.href}">${f.displayPath}</a>
|
||||
</td>
|
||||
<td class="fileSize">
|
||||
${h.humanReadableByteSize(f.getFileSize())}
|
||||
</td>
|
||||
<td>
|
||||
<j:if test="${app.fingerprintMap.ready}">
|
||||
<a href="${baseURL}artifact/${f.href}/*fingerprint*/">
|
||||
<l:icon class="icon-fingerprint icon-sm"/>
|
||||
</a>
|
||||
<st:nbsp/>
|
||||
</j:if>
|
||||
<a href="${baseURL}artifact/${f.href}/*view*/">${%view}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</j:forEach>
|
||||
</table>
|
||||
</j:if>
|
||||
<!-- otherwise just show link to directory browser -->
|
||||
</t:summary>
|
||||
</j:if>
|
||||
</j:if>
|
||||
<j:choose>
|
||||
<j:when test="${build == null}">
|
||||
<!-- nothing to render -->
|
||||
</j:when>
|
||||
<j:when test="${build.hasCustomArtifactManager()}">
|
||||
<tbody class="artifact-list" data-url="${rootURL}/${build.url}artifactList" data-caption="${caption}"/>
|
||||
<st:adjunct includes="lib.hudson.artifactList"/>
|
||||
</j:when>
|
||||
<j:otherwise>
|
||||
<st:include it="${build}" page="artifactList"/>
|
||||
</j:otherwise>
|
||||
</j:choose>
|
||||
</j:jelly>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
|
||||
* Copyright 2025 CloudBees, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,25 +22,18 @@
|
|||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package hudson.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@link IOException} with linked exception.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @deprecated Just use {@link IOException}, which since Java 6 supports a cause.
|
||||
*/
|
||||
@Deprecated
|
||||
public class IOException2 extends IOException {
|
||||
|
||||
public IOException2(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public IOException2(String s, Throwable cause) {
|
||||
super(s, cause);
|
||||
}
|
||||
|
||||
}
|
||||
// TODO package this logic into a generic utility like renderOnDemand/refreshPart with Behaviour.specify
|
||||
var e = document.querySelector(".artifact-list");
|
||||
fetch(
|
||||
e.getAttribute("data-url") +
|
||||
"?" +
|
||||
new URLSearchParams({ caption: e.getAttribute("data-caption") }),
|
||||
).then((rsp) => {
|
||||
if (rsp.ok) {
|
||||
rsp.text().then((responseText) => {
|
||||
e.innerHTML = responseText;
|
||||
Behaviour.applySubtree(e);
|
||||
layoutUpdateCallback.call();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -37,8 +37,12 @@ THE SOFTWARE.
|
|||
This is useful for programmatically adding the context menu
|
||||
</st:attribute>
|
||||
<st:attribute name="hasMenu">
|
||||
If true, this breadcrumb item will include a '⌄' symbol to display a dropdown menu with items
|
||||
from the '{breadcrumb.href}/contextMenu' path. Since 2.361.
|
||||
If true, this breadcrumb item will display a dropdown menu with items
|
||||
from the '{breadcrumb.href}/contextMenu' path on hover. Since 2.361.
|
||||
</st:attribute>
|
||||
<st:attribute name="hasChildrenMenu">
|
||||
If true, this breadcrumb item will display a dropdown menu with items
|
||||
from the '{breadcrumb.href}/childrenContextMenu' path on hover. Since TODO.
|
||||
</st:attribute>
|
||||
</st:documentation>
|
||||
|
||||
|
@ -50,7 +54,7 @@ THE SOFTWARE.
|
|||
<span>${attrs.title}</span>
|
||||
</j:when>
|
||||
<j:otherwise>
|
||||
<a href="${attrs.href}" class="${attrs.hasMenu ? 'hoverable-model-link' : ''}">
|
||||
<a href="${attrs.href}" class="${attrs.hasMenu ? 'hoverable-model-link' : ''} ${attrs.hasChildrenMenu ? 'hoverable-children-model-link' : ''}">
|
||||
${attrs.title}
|
||||
</a>
|
||||
</j:otherwise>
|
||||
|
|
|
@ -44,7 +44,8 @@ THE SOFTWARE.
|
|||
<j:if test="${anc.object != app}">
|
||||
<l:breadcrumb title="${anc.object.displayName}"
|
||||
href="${anc.url}/"
|
||||
hasMenu="${h.isModelWithContextMenu(anc.object)}" />
|
||||
hasMenu="${h.isModelWithContextMenu(anc.object)}"
|
||||
hasChildrenMenu="${h.isModelWithChildren(anc.object)}" />
|
||||
</j:if>
|
||||
</j:if>
|
||||
</j:forEach>
|
||||
|
|
|
@ -157,8 +157,16 @@ public class HudsonPrivateSecurityRealmTest {
|
|||
}
|
||||
|
||||
@Issue("JENKINS-75533")
|
||||
public void ensureExpectedMessage() {
|
||||
public void ensureExpectedMessageAscii() {
|
||||
final IllegalArgumentException ex = Assert.assertThrows(IllegalArgumentException.class, () -> HudsonPrivateSecurityRealm.PASSWORD_HASH_ENCODER.encode("1234567890123456789012345678901234567890123456789012345678901234567890123"));
|
||||
assertThat(ex.getMessage(), is(Messages.HudsonPrivateSecurityRealm_CreateAccount_BCrypt_PasswordTooLong_ASCII()));
|
||||
}
|
||||
|
||||
@Issue("JENKINS-75533")
|
||||
public void ensureExpectedMessageEmoji() {
|
||||
final IllegalArgumentException ex = Assert.assertThrows(IllegalArgumentException.class, () -> HudsonPrivateSecurityRealm.PASSWORD_HASH_ENCODER.encode(
|
||||
"\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20" +
|
||||
"\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20\uD83E\uDD20")); // 🤠
|
||||
assertThat(ex.getMessage(), is(Messages.HudsonPrivateSecurityRealm_CreateAccount_BCrypt_PasswordTooLong()));
|
||||
}
|
||||
}
|
||||
|
|
16
package.json
16
package.json
|
@ -23,18 +23,18 @@
|
|||
"lint": "yarn lint:js && yarn lint:css"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.27.1",
|
||||
"@babel/cli": "7.27.2",
|
||||
"@babel/core": "7.27.1",
|
||||
"@babel/preset-env": "7.27.1",
|
||||
"@eslint/js": "9.25.1",
|
||||
"@babel/preset-env": "7.27.2",
|
||||
"@eslint/js": "9.26.0",
|
||||
"babel-loader": "10.0.0",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"css-loader": "7.1.2",
|
||||
"css-minimizer-webpack-plugin": "7.0.2",
|
||||
"eslint": "9.25.1",
|
||||
"eslint-config-prettier": "10.1.2",
|
||||
"eslint": "9.26.0",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"eslint-formatter-checkstyle": "8.40.0",
|
||||
"globals": "16.0.0",
|
||||
"globals": "16.1.0",
|
||||
"handlebars-loader": "1.7.3",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"postcss": "8.5.3",
|
||||
|
@ -42,13 +42,13 @@
|
|||
"postcss-preset-env": "10.1.6",
|
||||
"postcss-scss": "4.0.9",
|
||||
"prettier": "3.5.3",
|
||||
"sass": "1.87.0",
|
||||
"sass": "1.88.0",
|
||||
"sass-loader": "16.0.5",
|
||||
"style-loader": "4.0.0",
|
||||
"stylelint": "16.19.1",
|
||||
"stylelint-checkstyle-reporter": "1.0.0",
|
||||
"stylelint-config-standard-scss": "14.0.0",
|
||||
"webpack": "5.99.7",
|
||||
"webpack": "5.99.8",
|
||||
"webpack-cli": "6.0.1",
|
||||
"webpack-remove-empty-scripts": "1.0.4"
|
||||
},
|
||||
|
|
10
pom.xml
10
pom.xml
|
@ -28,7 +28,7 @@ THE SOFTWARE.
|
|||
<parent>
|
||||
<groupId>org.jenkins-ci</groupId>
|
||||
<artifactId>jenkins</artifactId>
|
||||
<version>1.131</version>
|
||||
<version>1.132</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
|
@ -73,9 +73,9 @@ THE SOFTWARE.
|
|||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
<revision>2.509</revision>
|
||||
<revision>2.511</revision>
|
||||
<changelist>-SNAPSHOT</changelist>
|
||||
<project.build.outputTimestamp>2025-04-29T13:33:24Z</project.build.outputTimestamp>
|
||||
<project.build.outputTimestamp>2025-05-13T14:33:55Z</project.build.outputTimestamp>
|
||||
|
||||
<!-- configuration for patch tracker plugin -->
|
||||
<project.patchManagement.system>github</project.patchManagement.system>
|
||||
|
@ -87,7 +87,7 @@ THE SOFTWARE.
|
|||
<changelog.url>https://www.jenkins.io/changelog</changelog.url>
|
||||
|
||||
<!-- Bundled Remoting version -->
|
||||
<remoting.version>3307.v632ed11b_3a_c7</remoting.version>
|
||||
<remoting.version>3309.v27b_9314fd1a_4</remoting.version>
|
||||
|
||||
<spotbugs.effort>Max</spotbugs.effort>
|
||||
<spotbugs.threshold>Medium</spotbugs.threshold>
|
||||
|
@ -97,7 +97,7 @@ THE SOFTWARE.
|
|||
<bridge-method-injector.version>1.31</bridge-method-injector.version>
|
||||
<spotless.check.skip>false</spotless.check.skip>
|
||||
<!-- Make sure to keep the jetty-ee9-maven-plugin version in war/pom.xml in sync with the Jetty release in Winstone: -->
|
||||
<winstone.version>8.8</winstone.version>
|
||||
<winstone.version>8.9</winstone.version>
|
||||
<node.version>22.15.0</node.version>
|
||||
</properties>
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ function generateJumplistAccessors() {
|
|||
*/
|
||||
function generateDropdowns() {
|
||||
behaviorShim.specify(
|
||||
".hoverable-model-link",
|
||||
".hoverable-model-link, .hoverable-children-model-link",
|
||||
"-hoverable-dropdown-",
|
||||
1000,
|
||||
(element) =>
|
||||
|
@ -45,17 +45,65 @@ function generateDropdowns() {
|
|||
return;
|
||||
}
|
||||
|
||||
fetch(Path.combinePath(href, "contextMenu"))
|
||||
.then((response) => response.json())
|
||||
.then((json) =>
|
||||
instance.setContent(
|
||||
Utils.generateDropdownItems(
|
||||
mapChildrenItemsToDropdownItems(json.items),
|
||||
),
|
||||
),
|
||||
)
|
||||
.catch((error) => console.log(`Jumplist request failed: ${error}`))
|
||||
.finally(() => (instance.loaded = true));
|
||||
const hasModelLink = element.classList.contains(
|
||||
"hoverable-model-link",
|
||||
);
|
||||
const hasChildrenLink = element.classList.contains(
|
||||
"hoverable-children-model-link",
|
||||
);
|
||||
|
||||
const sections = {
|
||||
model: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
const fetchSection = function (urlSuffix) {
|
||||
return fetch(Path.combinePath(href, urlSuffix))
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
const items = mapChildrenItemsToDropdownItems(json.items);
|
||||
const section = document.createElement("div");
|
||||
section.appendChild(Utils.generateDropdownItems(items));
|
||||
return section;
|
||||
});
|
||||
};
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (hasModelLink) {
|
||||
promises.push(
|
||||
fetchSection("contextMenu").then((section) => {
|
||||
sections.model = section;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (hasChildrenLink) {
|
||||
promises.push(
|
||||
fetchSection("childrenContextMenu").then((section) => {
|
||||
sections.children = section;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
const container = document.createElement("div");
|
||||
container.className = "jenkins-dropdown__split-container";
|
||||
if (sections.model) {
|
||||
container.appendChild(sections.model);
|
||||
}
|
||||
if (sections.children) {
|
||||
container.appendChild(sections.children);
|
||||
}
|
||||
instance.setContent(container);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`Dropdown fetch failed: ${error}`);
|
||||
})
|
||||
.finally(() => {
|
||||
instance.loaded = true;
|
||||
});
|
||||
},
|
||||
false,
|
||||
{
|
||||
|
@ -74,6 +122,7 @@ function generateDropdowns() {
|
|||
(element) =>
|
||||
Utils.generateDropdown(element, (instance) => {
|
||||
const href = element.dataset.href;
|
||||
|
||||
const jumplistType = !element.classList.contains("children")
|
||||
? "contextMenu"
|
||||
: "childrenContextMenu";
|
||||
|
|
|
@ -288,3 +288,21 @@ $dropdown-padding: 0.375rem;
|
|||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.jenkins-dropdown__split-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
|
||||
& > * {
|
||||
min-width: 215px;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
background: color-mix(
|
||||
in sRGB,
|
||||
var(--text-color-secondary) 5%,
|
||||
transparent
|
||||
);
|
||||
border-left: var(--jenkins-border--subtle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>io.jenkins.plugins</groupId>
|
||||
<artifactId>ionicons-api</artifactId>
|
||||
<version>87.ve74b_60c10b_57</version>
|
||||
<version>88.va_4187cb_eddf1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jenkins.plugins</groupId>
|
||||
|
@ -123,7 +123,7 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-api</artifactId>
|
||||
<version>1371.ve334280b_d611</version>
|
||||
<version>1373.v7b_813f10efa_b_</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
|
@ -183,7 +183,7 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci.main</groupId>
|
||||
<artifactId>jenkins-test-harness</artifactId>
|
||||
<version>2443.v1f61c6816c2c</version>
|
||||
<version>2447.v8a_ce9e0fca_32</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
@ -235,7 +235,7 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>1322.v1556dc1c59a_f</version>
|
||||
<version>1334.vd3b_b_2094e438</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -27,6 +27,7 @@ package hudson.model;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.isA;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
@ -38,18 +39,23 @@ import hudson.FilePath;
|
|||
import hudson.Launcher;
|
||||
import hudson.XmlFile;
|
||||
import hudson.model.listeners.SaveableListener;
|
||||
import hudson.remoting.Callable;
|
||||
import hudson.tasks.ArtifactArchiver;
|
||||
import hudson.tasks.BuildTrigger;
|
||||
import hudson.tasks.Builder;
|
||||
import hudson.tasks.Fingerprinter;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
import jenkins.model.ArtifactManager;
|
||||
import jenkins.model.ArtifactManagerConfiguration;
|
||||
import jenkins.model.ArtifactManagerFactory;
|
||||
|
@ -71,6 +77,8 @@ import org.kohsuke.stapler.DataBoundConstructor;
|
|||
@Category(SmokeTest.class)
|
||||
public class RunTest {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(RunTest.class.getName());
|
||||
|
||||
@Rule public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Issue("JENKINS-17935")
|
||||
|
@ -276,4 +284,104 @@ public class RunTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test public void slowArtifactManager() throws Exception {
|
||||
ArtifactManagerConfiguration.get().getArtifactManagerFactories().add(new SlowMgr.Factory());
|
||||
var p = j.createFreeStyleProject();
|
||||
j.jenkins.getWorkspaceFor(p).child("f").write("", null);
|
||||
p.getPublishersList().add(new ArtifactArchiver("f"));
|
||||
var b = j.buildAndAssertSuccess(p);
|
||||
assertThat(b.getArtifactManager(), isA(SlowMgr.class));
|
||||
var wc = j.createWebClient();
|
||||
wc.getOptions().setJavaScriptEnabled(false);
|
||||
wc.getPage(b);
|
||||
wc.getPage(p);
|
||||
}
|
||||
|
||||
public static final class SlowMgr extends ArtifactManager {
|
||||
static final AtomicBoolean deleted = new AtomicBoolean();
|
||||
|
||||
@Override public boolean delete() {
|
||||
return !deleted.getAndSet(true);
|
||||
}
|
||||
|
||||
@Override public void onLoad(Run<?, ?> build) {}
|
||||
|
||||
@Override public void archive(FilePath workspace, Launcher launcher, BuildListener listener, Map<String, String> artifacts) {
|
||||
LOGGER.info(() -> "Pretending to archive " + artifacts);
|
||||
}
|
||||
|
||||
@Override public VirtualFile root() {
|
||||
return new VirtualFile() {
|
||||
@Override public <V> V run(Callable<V, IOException> callable) throws IOException {
|
||||
LOGGER.info("Sleeping");
|
||||
try {
|
||||
Thread.sleep(Long.MAX_VALUE);
|
||||
} catch (InterruptedException x) {
|
||||
throw new IOException(x);
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override public String getName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override public URI toURI() {
|
||||
return URI.create("no://where");
|
||||
}
|
||||
|
||||
@Override public VirtualFile getParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public boolean isDirectory() throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean isFile() throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean exists() throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public VirtualFile[] list() throws IOException {
|
||||
return new VirtualFile[0];
|
||||
}
|
||||
|
||||
@Override public VirtualFile child(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public long length() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override public long lastModified() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override public boolean canRead() throws IOException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public InputStream open() throws IOException {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final class Factory extends ArtifactManagerFactory {
|
||||
@DataBoundConstructor public Factory() {}
|
||||
|
||||
@Override public ArtifactManager managerFor(Run<?, ?> build) {
|
||||
LOGGER.info(() -> "Picking manager for " + build);
|
||||
return new SlowMgr();
|
||||
}
|
||||
|
||||
@TestExtension("slowArtifactManager") public static final class DescriptorImpl extends ArtifactManagerFactoryDescriptor {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package hudson.node_monitors;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
@ -11,6 +12,8 @@ import hudson.model.User;
|
|||
import hudson.slaves.DumbSlave;
|
||||
import hudson.slaves.OfflineCause;
|
||||
import hudson.slaves.SlaveComputer;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.InboundAgentRule;
|
||||
|
@ -47,8 +50,15 @@ public class ResponseTimeMonitorTest {
|
|||
j.disconnectSlave(s);
|
||||
assertNull(ResponseTimeMonitor.DESCRIPTOR.monitor(c));
|
||||
|
||||
// Now reconnect and make sure we get a non-null response.
|
||||
c.connect(false).get(); // wait until it's connected
|
||||
// Retry to compensate for test being flaky in CI
|
||||
await().atMost(5, TimeUnit.SECONDS)
|
||||
.ignoreException(ExecutionException.class)
|
||||
.until(() -> {
|
||||
// Now reconnect and make sure we get a non-null response.
|
||||
c.connect(false).get(); // wait until it's connected
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
assertNotNull(ResponseTimeMonitor.DESCRIPTOR.monitor(c));
|
||||
}
|
||||
|
|
|
@ -30,12 +30,20 @@ import static org.junit.Assert.assertNull;
|
|||
|
||||
import hudson.model.InvisibleAction;
|
||||
import hudson.model.RootAction;
|
||||
import hudson.widgets.RenderOnDemandClosure;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.logging.Level;
|
||||
import org.htmlunit.ScriptResult;
|
||||
import org.htmlunit.WebClientUtil;
|
||||
import org.htmlunit.html.HtmlPage;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.Issue;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
import org.jvnet.hudson.test.LoggerRule;
|
||||
import org.jvnet.hudson.test.MemoryAssert;
|
||||
import org.jvnet.hudson.test.TestExtension;
|
||||
|
||||
/**
|
||||
|
@ -46,6 +54,7 @@ import org.jvnet.hudson.test.TestExtension;
|
|||
public class RenderOnDemandTest {
|
||||
|
||||
@Rule public JenkinsRule j = new JenkinsRule();
|
||||
@Rule public LoggerRule logging = new LoggerRule().record(RenderOnDemandClosure.class, Level.FINE);
|
||||
|
||||
/**
|
||||
* Makes sure that the behavior rules are applied to newly inserted nodes,
|
||||
|
@ -64,27 +73,40 @@ public class RenderOnDemandTest {
|
|||
assertEquals("AlphaBravoCharlie", r.getJavaScriptResult().toString());
|
||||
}
|
||||
|
||||
/*
|
||||
@Ignore("just informational")
|
||||
@Issue("JENKINS-16341")
|
||||
@Test
|
||||
public void testMemoryConsumption() throws Exception {
|
||||
j.createWebClient().goTo("self/testBehaviour"); // prime caches
|
||||
var wc = j.createWebClient();
|
||||
callTestBehaviour(wc); // prime caches
|
||||
int total = 0;
|
||||
for (MemoryAssert.HistogramElement element : MemoryAssert.increasedMemory(new Callable<Void>() {
|
||||
@Override public Void call() throws Exception {
|
||||
j.createWebClient().goTo("self/testBehaviour");
|
||||
return null;
|
||||
int count = 50;
|
||||
for (var element : MemoryAssert.increasedMemory(() -> {
|
||||
for (int i = 0; i < count; i++) {
|
||||
System.err.println("#" + i);
|
||||
callTestBehaviour(wc);
|
||||
}
|
||||
}, new Filter() {
|
||||
@Override public boolean accept(Object obj, Object referredFrom, Field reference) {
|
||||
return !obj.getClass().getName().contains("htmlunit");
|
||||
}
|
||||
})) {
|
||||
var o = new Object();
|
||||
var sr = new SoftReference<>(o);
|
||||
var wr = new WeakReference<>(o);
|
||||
o = null;
|
||||
MemoryAssert.assertGC(wr, true);
|
||||
return null;
|
||||
}, (obj, referredFrom, reference) -> !obj.getClass().getName().contains("htmlunit"))) {
|
||||
total += element.byteSize;
|
||||
if (element.instanceCount == count) {
|
||||
System.out.print("⚠ ️");
|
||||
}
|
||||
System.out.println(element.className + " ×" + element.instanceCount + ": " + element.byteSize);
|
||||
}
|
||||
System.out.println("total: " + total);
|
||||
}
|
||||
*/
|
||||
|
||||
private void callTestBehaviour(JenkinsRule.WebClient wc) throws Exception {
|
||||
var p = wc.goTo("self/testBehaviour");
|
||||
p.executeJavaScript("renderOnDemand(document.getElementsBySelector('.lazy')[0])");
|
||||
WebClientUtil.waitForJSExec(p.getWebClient());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that scripts get evaluated.
|
||||
|
|
|
@ -79,6 +79,25 @@ public class BindTest {
|
|||
assertThat(root.invocations, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWithWellKnownURLWithQuotes() throws Exception {
|
||||
final RootActionWithWellKnownURLWithQuotes root = ExtensionList.lookupSingleton(RootActionWithWellKnownURLWithQuotes.class);
|
||||
try (JenkinsRule.WebClient wc = j.createWebClient()) {
|
||||
final HtmlPage htmlPage = wc.goTo(root.getUrlName());
|
||||
final String scriptUrl = htmlPage
|
||||
.getElementsByTagName("script")
|
||||
.stream()
|
||||
.filter(it -> it.getAttribute("src").startsWith(j.contextPath + "/$stapler/bound/script" + j.contextPath + "/the'Well'Known\\'Root'With'Quotes?"))
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.getAttribute("src");
|
||||
|
||||
final Page script = wc.goTo(StringUtils.removeStart(scriptUrl, j.contextPath + "/"), "text/javascript");
|
||||
assertThat(script.getWebResponse().getContentAsString(), is("varname = makeStaplerProxy('" + j.contextPath + "/the\\'Well\\'Known\\\\\\'Root\\'With\\'Quotes','test',['annotatedJsMethod2','byName2']);"));
|
||||
}
|
||||
assertThat(root.invocations, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindNull() throws Exception {
|
||||
final RootActionImpl root = ExtensionList.lookupSingleton(RootActionImpl.class);
|
||||
|
@ -173,4 +192,26 @@ public class BindTest {
|
|||
invocations++;
|
||||
}
|
||||
}
|
||||
|
||||
@TestExtension
|
||||
public static class RootActionWithWellKnownURLWithQuotes extends InvisibleAction implements RootAction, WithWellKnownURL {
|
||||
private int invocations;
|
||||
|
||||
@Override
|
||||
public String getUrlName() {
|
||||
return "the'Well'Known\\'Root'With'Quotes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWellKnownUrl() {
|
||||
return "/" + getUrlName();
|
||||
}
|
||||
|
||||
@JavaScriptMethod
|
||||
public void annotatedJsMethod2(String foo) {}
|
||||
|
||||
public void jsByName2() {
|
||||
invocations++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:st="jelly:stapler">
|
||||
<l:layout title="The Root">
|
||||
<l:main-panel>
|
||||
<h1>Root Action</h1>
|
||||
<st:bind var="varname" value="${it}"/>
|
||||
<st:adjunct includes="org.kohsuke.stapler.BindTest.RootActionWithWellKnownURL.adjunct"/>
|
||||
</l:main-panel>
|
||||
</l:layout>
|
||||
</j:jelly>
|
|
@ -317,14 +317,14 @@ THE SOFTWARE.
|
|||
<!-- detached after 1.577 -->
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>1322.v1556dc1c59a_f</version>
|
||||
<version>1334.vd3b_b_2094e438</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<!-- dependency of junit, plugin-util-api, and workflow-support -->
|
||||
<groupId>org.jenkins-ci.plugins.workflow</groupId>
|
||||
<artifactId>workflow-api</artifactId>
|
||||
<version>1371.ve334280b_d611</version>
|
||||
<version>1373.v7b_813f10efa_b_</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
|
@ -533,7 +533,7 @@ THE SOFTWARE.
|
|||
<!-- dependency of junit and matrix-auth -->
|
||||
<groupId>io.jenkins.plugins</groupId>
|
||||
<artifactId>ionicons-api</artifactId>
|
||||
<version>87.ve74b_60c10b_57</version>
|
||||
<version>88.va_4187cb_eddf1</version>
|
||||
<type>hpi</type>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
|
@ -653,7 +653,7 @@ THE SOFTWARE.
|
|||
contains a version of Jetty that is older than this, trigger Dependabot in jenkinsci/winstone and release the
|
||||
result before proceeding with the update here.
|
||||
-->
|
||||
<version>12.0.20</version>
|
||||
<version>12.0.21</version>
|
||||
<configuration>
|
||||
<!--
|
||||
Reload webapp when you hit ENTER. (See JETTY-282 for more)
|
||||
|
|
Loading…
Reference in New Issue