View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.background;
16  
17  import static org.junit.Assert.assertEquals;
18  import static org.junit.Assert.assertTrue;
19  
20  import org.apache.commons.lang3.mutable.MutableInt;
21  import org.htmlunit.Page;
22  import org.htmlunit.WebClient;
23  import org.htmlunit.WebWindow;
24  import org.junit.After;
25  import org.junit.Before;
26  import org.junit.Test;
27  
28  /**
29   * Minimal tests for {@link JavaScriptJobManagerImpl}. Tests which use the full HtmlUnit stack
30   * go in {@link JavaScriptJobManagerTest}.
31   *
32   * @author Daniel Gredler
33   * @author Amit Manjhi
34   * @author Ronald Brill
35   */
36  public class JavaScriptJobManagerMinimalTest {
37  
38      private WebClient client_;
39      private WebWindow window_;
40      private Page page_;
41      private JavaScriptJobManager manager_;
42      private DefaultJavaScriptExecutor eventLoop_;
43      enum WaitingMode {
44          WAIT_STARTING_BEFORE, WAIT_TIMELIMIT,
45      }
46  
47      /**
48       * Initializes variables required by the unit tests.
49       */
50      @Before
51      public void before() {
52          client_ = new WebClient();
53          window_ = client_.getCurrentWindow();
54          page_ = window_.getEnclosedPage();
55          manager_ = window_.getJobManager();
56          eventLoop_ = new DefaultJavaScriptExecutor(client_);
57          eventLoop_.addWindow(window_);
58      }
59  
60      /**
61       * Shuts down the event loop.
62       */
63      @After
64      public void after() {
65          eventLoop_.shutdown();
66          if (client_ != null) {
67              client_.close();
68          }
69      }
70  
71      /**
72       * Didn't pass reliably.
73       * @throws Exception if an error occurs
74       */
75      @Test
76      public void addJob_periodicJob() throws Exception {
77          final MutableInt count = new MutableInt(0);
78          final JavaScriptJob job = new BasicJavaScriptJob(5, Integer.valueOf(100)) {
79              @Override
80              public void run() {
81                  count.increment();
82              }
83          };
84          manager_.addJob(job, page_);
85          assertEquals(1, manager_.getJobCount());
86          final int remainingJobs = manager_.waitForJobs(1090);
87          assertTrue("At least one remaining job expected.", remainingJobs >= 1);
88          assertTrue("Less than 10 jobs (" + count.intValue() + ") processed.", count.intValue() >= 10);
89      }
90  
91      /**
92       * Test for changes of revision 5589.
93       * Ensures that interval jobs are scheduled at fix rate, no matter how long each one takes.
94       * This test failed as of revision 5588 as consequence of the single threaded JS execution changes.
95       * @throws Exception if an error occurs
96       */
97      @Test
98      public void addJob_periodicJob2() throws Exception {
99          final MutableInt count = new MutableInt(0);
100         final JavaScriptJob job = new BasicJavaScriptJob(5, Integer.valueOf(200)) {
101             @Override
102             public void run() {
103                 if (count.intValue() == 0) {
104                     try {
105                         Thread.sleep(200);
106                     }
107                     catch (final InterruptedException e) {
108                         // ignore
109                     }
110                 }
111                 count.increment();
112             }
113         };
114         manager_.addJob(job, page_);
115         final int remainingJobs = manager_.waitForJobs(300);
116         assertTrue(remainingJobs >= 1);
117         // first interval starts at 5 and ends at 205
118         // with a fix delay (what would be wrong), we would have second interval start at 205+200 = 405
119         // and therefore count == 1
120         // with a fix rate (correct), second interval starts at 205 and therefore count == 2
121         assertEquals(2, count.intValue());
122     }
123 
124     /**
125      * @throws Exception if an error occurs
126      */
127     @Test
128     public void addJob_singleExecution() throws Exception {
129         final MutableInt count = new MutableInt(0);
130         final JavaScriptJob job = new BasicJavaScriptJob(5, null) {
131             @Override
132             public void run() {
133                 count.increment();
134             }
135         };
136         manager_.addJob(job, page_);
137         assertEquals(1, manager_.getJobCount());
138         manager_.waitForJobs(1000);
139         assertEquals(1, count.intValue());
140     }
141 
142     /**
143      * @throws Exception if an error occurs
144      */
145     @Test
146     public void addJob_multipleExecution_removeJob() throws Exception {
147         final MutableInt id = new MutableInt();
148         final MutableInt count = new MutableInt(0);
149         final JavaScriptJob job = new BasicJavaScriptJob(50, Integer.valueOf(50)) {
150             @Override
151             public void run() {
152                 count.increment();
153                 if (count.intValue() >= 5) {
154                     manager_.removeJob(id.intValue());
155                 }
156             }
157         };
158         id.setValue(manager_.addJob(job, page_));
159         manager_.waitForJobs(1000);
160         assertEquals(5, count.intValue());
161     }
162 
163     /**
164      * @throws Exception if an error occurs
165      */
166     @Test
167     public void addJob_multipleExecution_removeAllJobs() throws Exception {
168         final MutableInt count = new MutableInt(0);
169         final JavaScriptJob job = new BasicJavaScriptJob(50, Integer.valueOf(50)) {
170             @Override
171             public void run() {
172                 count.increment();
173                 if (count.intValue() >= 5) {
174                     manager_.removeAllJobs();
175                 }
176             }
177         };
178         manager_.addJob(job, page_);
179         manager_.waitForJobs(1000);
180         assertEquals(5, count.intValue());
181     }
182 
183     /**
184      * @throws Exception if an error occurs
185      */
186     @Test
187     public void getJobCount() throws Exception {
188         final MutableInt count = new MutableInt();
189         final JavaScriptJob job = new BasicJavaScriptJob(50, null) {
190             @Override
191             public void run() {
192                 count.setValue(manager_.getJobCount());
193             }
194         };
195         assertEquals(0, manager_.getJobCount());
196         manager_.addJob(job, page_);
197         manager_.waitForJobs(1000);
198         assertEquals(1, count.intValue());
199         assertEquals(0, manager_.getJobCount());
200     }
201 
202     private void waitForCurrentLongJob(final WaitingMode waitingMode, final int expectedFinalJobCount) {
203         final JavaScriptJob job = new BasicJavaScriptJob(50, null) {
204             // Long job
205             @Override
206             public void run() {
207                 try {
208                     Thread.sleep(500);
209                 }
210                 catch (final InterruptedException expected) {
211                     // this is normal
212                 }
213             }
214         };
215         assertEquals(0, manager_.getJobCount());
216         manager_.addJob(job, page_);
217         final long delayMillis = 100;
218         switch (waitingMode) {
219             case WAIT_STARTING_BEFORE:
220                 manager_.waitForJobsStartingBefore(delayMillis);
221                 break;
222             case WAIT_TIMELIMIT:
223                 manager_.waitForJobs(delayMillis);
224                 break;
225             default:
226                 throw new IllegalArgumentException("Not handled");
227         }
228         assertEquals(expectedFinalJobCount, manager_.getJobCount());
229     }
230 
231     /**
232      * Tests the waitForJobs call when there is an executing long job.
233      */
234     @Test
235     public void waitForJobs_currentLongJob() {
236         waitForCurrentLongJob(WaitingMode.WAIT_TIMELIMIT, 1);
237     }
238 
239     /**
240      * Tests the waitForJobsStartingBefore call when there is an executing long job.
241      */
242     @Test
243     public void waitForJobsStartingBefore_currentLongJob() {
244         waitForCurrentLongJob(WaitingMode.WAIT_STARTING_BEFORE, 0);
245     }
246 
247     private void waitForSimpleJobs(final WaitingMode waitingMode, final int expectedFinalJobCount) {
248         final JavaScriptJob job1 = new BasicJavaScriptJob(50, null) {
249             @Override
250             public void run() {
251             // Empty.
252             }
253         };
254         final JavaScriptJob job2 = new BasicJavaScriptJob(1000, null) {
255             @Override
256             public void run() {
257             // Empty.
258             }
259         };
260         assertEquals(0, manager_.getJobCount());
261         manager_.addJob(job1, page_);
262         manager_.addJob(job2, page_);
263         final long delayMillis = 250;
264         switch (waitingMode) {
265             case WAIT_STARTING_BEFORE:
266                 manager_.waitForJobsStartingBefore(delayMillis);
267                 break;
268             case WAIT_TIMELIMIT:
269                 manager_.waitForJobs(delayMillis);
270                 break;
271             default:
272                 throw new IllegalArgumentException("Not handled");
273         }
274         assertEquals(expectedFinalJobCount, manager_.getJobCount());
275     }
276 
277     /**
278      * @throws Exception if an error occurs
279      */
280     @Test
281     public void waitForJobs_simpleJobs() throws Exception {
282         waitForSimpleJobs(WaitingMode.WAIT_TIMELIMIT, 1);
283     }
284 
285     /**
286      * @throws Exception if an error occurs
287      */
288     @Test
289     public void waitForJobsStartingBefore_simpleJobs() throws Exception {
290         waitForSimpleJobs(WaitingMode.WAIT_STARTING_BEFORE, 1);
291     }
292 
293     private void waitForComplexJobs(final WaitingMode waitingMode, final int expectedFinalJobCount) {
294         final long start = System.currentTimeMillis();
295         final JavaScriptJob job1 = new BasicJavaScriptJob(50, null) {
296             // This job takes 30ms to complete.
297             @Override
298             public void run() {
299                 try {
300                     Thread.sleep(30);
301                 }
302                 catch (final InterruptedException expected) {
303                     // this is normal
304                 }
305             }
306         };
307         final JavaScriptJob job2 = new BasicJavaScriptJob(60, null) {
308             @Override
309             public void run() {
310             // Empty.
311             }
312         };
313         assertEquals(0, manager_.getJobCount());
314         manager_.addJob(job1, page_);
315         manager_.addJob(job2, page_);
316         // sometimes it takes some time to reach this point
317         // to make this test stable we have to take care of that
318         final long delayMillis = 70 - (System.currentTimeMillis() - start);
319         switch (waitingMode) {
320             case WAIT_STARTING_BEFORE:
321                 manager_.waitForJobsStartingBefore(delayMillis);
322                 break;
323             case WAIT_TIMELIMIT:
324                 manager_.waitForJobs(delayMillis);
325                 break;
326             default:
327                 throw new RuntimeException("Unknown value for waitingMode enum " + waitingMode);
328         }
329         assertEquals(expectedFinalJobCount, manager_.getJobCount());
330     }
331 
332     /**
333      * @throws Exception if an error occurs
334      */
335     @Test
336     public void waitForJobs_complexJobs() throws Exception {
337         //job1 is still running, job2 has not started.
338         waitForComplexJobs(WaitingMode.WAIT_TIMELIMIT, 2);
339     }
340 
341     /**
342      * @throws Exception if an error occurs
343      */
344     @Test
345     public void waitForJobsStartingBefore_complexJobs() throws Exception {
346         // the call waits until both job1 and job2 finish.
347         waitForComplexJobs(WaitingMode.WAIT_STARTING_BEFORE, 0);
348     }
349 }