4040import hudson .model .Job ;
4141import hudson .model .Result ;
4242import hudson .model .Run ;
43- import hudson .model .TaskListener ;
4443import hudson .model .listeners .RunListener ;
4544import hudson .tasks .BuildStepDescriptor ;
4645import hudson .tasks .BuildStepMonitor ;
5352import java .util .Collection ;
5453import java .util .Collections ;
5554import java .util .List ;
55+ import java .util .Set ;
56+ import java .util .stream .Collectors ;
5657import jenkins .model .Jenkins ;
5758import net .sf .json .JSONObject ;
5859import org .kohsuke .accmod .Restricted ;
@@ -93,7 +94,7 @@ public AggregatedTestResultPublisher(String jobs, boolean includeFailedBuilds) {
9394 public boolean perform (AbstractBuild <?, ?> build , Launcher launcher , BuildListener listener )
9495 throws InterruptedException , IOException {
9596 // add a TestResult just so that it can show up later.
96- build .addAction (new TestResultAction (jobs , includeFailedBuilds , build ));
97+ build .addAction (new TestResultAction (jobs , includeFailedBuilds ));
9798 return true ;
9899 }
99100
@@ -126,13 +127,13 @@ public static final class TestResultAction extends AbstractTestResultAction {
126127 private final boolean includeFailedBuilds ;
127128
128129 /**
129- * The last time the fields of this object is computed from the rest .
130+ * The reference of the lastChangedReference when we last recomputed the aggregation. if this is != lastChangedReference then we need to recomupte the results .
130131 */
131- private transient long lastUpdated = 0 ;
132+ private transient Object lastRecomputedReference = null ;
132133 /**
133- * When was the last time any build completed?
134+ * Unique Object set whenever a build completes.
134135 */
135- private static long lastChanged = 0 ;
136+ private static volatile Object lastChangedReference = new Object () ;
136137
137138 private transient int failCount ;
138139 private transient int totalCount ;
@@ -144,43 +145,39 @@ public static final class TestResultAction extends AbstractTestResultAction {
144145
145146 private transient List <AbstractProject > noFingerprints ;
146147
147- @ SuppressWarnings ("deprecation" ) // calls getProject in constructor, so needs owner immediately
148+ /**
149+ * @deprecated use {@link #TestResultAction(String, boolean)}
150+ */
151+ @ Deprecated (forRemoval = true )
148152 public TestResultAction (String jobs , boolean includeFailedBuilds , AbstractBuild <?, ?> owner ) {
149- super ( owner );
150- this . includeFailedBuilds = includeFailedBuilds ;
153+ this ( jobs , includeFailedBuilds );
154+ }
151155
152- if (jobs == null ) {
153- // resolve null as the transitive downstream jobs
154- StringBuilder buf = new StringBuilder ();
155- for (AbstractProject p : getProject ().getTransitiveDownstreamProjects ()) {
156- if (buf .length () > 0 ) {
157- buf .append (',' );
158- }
159- buf .append (p .getFullName ());
160- }
161- jobs = buf .toString ();
162- }
156+ public TestResultAction (String jobs , boolean includeFailedBuilds ) {
157+ this .includeFailedBuilds = includeFailedBuilds ;
163158 this .jobs = jobs ;
164159 }
165160
166161 /**
167162 * Gets the jobs to be monitored.
168163 */
169164 public Collection <AbstractProject > getJobs () {
165+ if (jobs == null ) {
166+ Set <AbstractProject > projects = getProject ().getTransitiveDownstreamProjects ();
167+ return projects .stream ().filter (p -> p .hasPermission (Item .READ )).collect (Collectors .toSet ());
168+ }
170169 List <AbstractProject > r = new ArrayList <>();
171- if (jobs != null ) {
172- for (String job : Util .tokenize (jobs , "," )) {
173- try {
174- AbstractProject j = Jenkins .get ().getItemByFullName (job .trim (), AbstractProject .class );
175- if (j != null ) {
176- r .add (j );
177- }
178- } catch (RuntimeException x ) {
179- if (x .getClass ().getSimpleName ().startsWith ("AccessDeniedException" )) {
180- // just skip it
181- } else {
182- throw x ;
183- }
170+ for (String job : Util .tokenize (jobs , "," )) {
171+ try {
172+ AbstractProject j = Jenkins .get ().getItemByFullName (job .trim (), AbstractProject .class );
173+ if (j != null ) {
174+ r .add (j );
175+ }
176+ } catch (RuntimeException x ) {
177+ if (x .getClass ().getSimpleName ().startsWith ("AccessDeniedException" )) {
178+ // just skip it
179+ } else {
180+ throw x ;
184181 }
185182 }
186183 }
@@ -271,10 +268,10 @@ public List<AbstractProject> getNoFingerprints() {
271268 justification = "False positive. Short-circuited" )
272269 private synchronized void upToDateCheck () {
273270 // up to date check
274- if (lastUpdated > lastChanged ) {
271+ if (lastRecomputedReference == lastChangedReference ) {
275272 return ;
276273 }
277- lastUpdated = lastChanged + 1 ;
274+ lastRecomputedReference = lastChangedReference ;
278275
279276 int failCount = 0 ;
280277 int totalCount = 0 ;
@@ -350,9 +347,10 @@ public String getUrlName() {
350347
351348 @ Extension
352349 public static class RunListenerImpl extends RunListener <Run > {
350+ @ SuppressFBWarnings (value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" , justification = "intended by design" )
353351 @ Override
354- public void onCompleted (Run run , TaskListener listener ) {
355- lastChanged = System . currentTimeMillis ();
352+ public void onFinalized (Run run ) {
353+ lastChangedReference = new Object ();
356354 }
357355 }
358356 }
0 commit comments