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;
16  
17  import java.io.IOException;
18  import java.net.URL;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.htmlunit.html.HtmlPage;
26  import org.htmlunit.javascript.background.JavaScriptJobManager;
27  import org.htmlunit.junit.BrowserRunner;
28  import org.htmlunit.junit.annotation.Tries;
29  import org.junit.Test;
30  import org.junit.runner.RunWith;
31  
32  /**
33   * Tests for {@link WebClient#waitForBackgroundJavaScriptStartingBefore(long)} and
34   * {@link WebClient#waitForBackgroundJavaScript(long)}.
35   *
36   * @author Marc Guillemot
37   */
38  @RunWith(BrowserRunner.class)
39  public class WebClientWaitForBackgroundJobsTest extends SimpleWebTestCase {
40  
41      private long startTime_;
42  
43      private void startTimedTest() {
44          startTime_ = System.currentTimeMillis();
45      }
46  
47      private void assertMaxTestRunTime(final long maxRunTimeMilliseconds) {
48          final long endTime = System.currentTimeMillis();
49          final long runTime = endTime - startTime_;
50          assertTrue("\nTest took too long to run and results may not be accurate. Please try again. "
51              + "\n  Actual Run Time: "
52              + runTime
53              + "\n  Max Run Time: "
54              + maxRunTimeMilliseconds, runTime < maxRunTimeMilliseconds);
55      }
56  
57      /**
58       * @throws Exception if the test fails
59       */
60      @Test
61      public void dontWaitWhenUnnecessary() throws Exception {
62          final String content = DOCTYPE_HTML
63              + "<html>\n"
64              + "<head>\n"
65              + "  <title>test</title>\n"
66              + "  <script>\n"
67              + "    var threadID;\n"
68              + "    function test() {\n"
69              + "      threadID = setTimeout(doAlert, 10000);\n"
70              + "    }\n"
71              + "    function doAlert() {\n"
72              + "      alert('blah');\n"
73              + "    }\n"
74              + "  </script>\n"
75              + "</head>\n"
76              + "<body onload='test()'>\n"
77              + "</body>\n"
78              + "</html>";
79  
80          final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
81          final HtmlPage page = loadPage(content, collectedAlerts);
82          final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
83          assertNotNull(jobManager);
84          assertEquals(1, jobManager.getJobCount());
85  
86          startTimedTest();
87          assertEquals(1, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(7000));
88          assertMaxTestRunTime(100);
89          assertEquals(1, jobManager.getJobCount());
90          assertEquals(Collections.EMPTY_LIST, collectedAlerts);
91      }
92  
93      /**
94       * @throws Exception if the test fails
95       */
96      @Test
97      @Tries(3)
98      public void dontWaitWhenUnnecessary_jobRemovesOtherJob() throws Exception {
99          final String content = DOCTYPE_HTML
100             + "<html>\n"
101             + "<head>\n"
102             + "  <title>test</title>\n"
103             + "  <script>\n"
104             + "    var longTimeoutID;\n"
105             + "    function test() {\n"
106             + "      longTimeoutID = setTimeout(doAlert('late'), 10000);\n"
107             + "      setTimeout(clearLongTimeout, 100);\n"
108             + "      setTimeout(doAlert('hello'), 300);\n"
109             + "    }\n"
110             + "    function clearLongTimeout() {\n"
111             + "      alert('clearLongTimeout');\n"
112             + "      clearTimeout(longTimeoutID);\n"
113             + "    }\n"
114             + "    function doAlert(_text) {\n"
115             + "      return function doAlert() {\n"
116             + "        alert(_text);\n"
117             + "      }\n"
118             + "    }\n"
119             + "  </script>\n"
120             + "</head>\n"
121             + "<body onload='test()'>\n"
122             + "</body>\n"
123             + "</html>";
124 
125         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
126         final HtmlPage page = loadPage(content, collectedAlerts);
127         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
128         assertNotNull(jobManager);
129         assertEquals(3, jobManager.getJobCount());
130 
131         startTimedTest();
132         assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(20_000));
133         assertMaxTestRunTime(400);
134         assertEquals(0, jobManager.getJobCount());
135 
136         final String[] expectedAlerts = {"clearLongTimeout", "hello"};
137         assertEquals(expectedAlerts, collectedAlerts);
138     }
139 
140     /**
141      * When waitForBackgroundJavaScriptStartingBefore is called while a job is being executed, it has
142      * to wait for this job to finish, even if this clearXXX has been called for it.
143      * @throws Exception if the test fails
144      */
145     @Test
146     @Tries(3)
147     public void waitCalledDuringJobExecution() throws Exception {
148         final String html = DOCTYPE_HTML
149             + "<html>\n"
150             + "<head>\n"
151             + "  <title>test</title>\n"
152             + "  <script>\n"
153             + "    var intervalId;\n"
154             + "    function test() {\n"
155             + "      intervalId = setTimeout(doWork, 100);\n"
156             + "    }\n"
157             + "    function doWork() {\n"
158             + "      clearTimeout(intervalId);\n"
159             + "      // waitForBackgroundJavaScriptStartingBefore should be called when JS execution is here\n"
160             + "      var request = new XMLHttpRequest();\n"
161             + "      request.open('GET', 'wait', false);\n"
162             + "      request.send('');\n"
163             + "      alert('end work');\n"
164             + "    }\n"
165             + "  </script>\n"
166             + "</head>\n"
167             + "<body onload='test()'>\n"
168             + "</body>\n"
169             + "</html>";
170 
171         final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
172         final MockWebConnection webConnection = new MockWebConnection() {
173             @Override
174             public WebResponse getResponse(final WebRequest request) throws IOException {
175                 if (request.getUrl().toExternalForm().endsWith("/wait")) {
176                     threadSynchronizer.waitForState("just before waitForBackgroundJavaScriptStartingBefore");
177                     threadSynchronizer.sleep(400); // main thread need to be able to process next instruction
178                 }
179                 return super.getResponse(request);
180             }
181         };
182         webConnection.setResponse(URL_FIRST, html);
183         webConnection.setDefaultResponse("");
184 
185         final WebClient client = getWebClient();
186         client.setWebConnection(webConnection);
187 
188         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
189         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
190 
191         final HtmlPage page = client.getPage(URL_FIRST);
192         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
193         assertNotNull(jobManager);
194         assertEquals(1, jobManager.getJobCount());
195 
196         startTimedTest();
197         threadSynchronizer.setState("just before waitForBackgroundJavaScriptStartingBefore");
198         assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
199         assertMaxTestRunTime(600);
200         assertEquals(0, jobManager.getJobCount());
201 
202         final String[] expectedAlerts = {"end work"};
203         assertEquals(expectedAlerts, collectedAlerts);
204     }
205 
206     /**
207      * When waitForBackgroundJavaScriptStartingBefore is called and a new job is scheduled after the one that
208      * is first found as the last one within the delay (a job starts a new one or simply a setInterval),
209      * a few retries should be done to see if new jobs exists.
210      * @throws Exception if the test fails
211      */
212     @Test
213     public void waitWhenLastJobStartsNewOne() throws Exception {
214         final String html = DOCTYPE_HTML
215             + "<html>\n"
216             + "<head>\n"
217             + "  <title>test</title>\n"
218             + "  <script>\n"
219             + "    function test() {\n"
220             + "      setTimeout(doWork1, 200);\n"
221             + "    }\n"
222             + "    function doWork1() {\n"
223             + "      alert('work1');\n"
224             + "      setTimeout(doWork2, 200);\n"
225             + "    }\n"
226             + "    function doWork2() {\n"
227             + "      alert('work2');\n"
228             + "    }\n"
229             + "  </script>\n"
230             + "</head>\n"
231             + "<body onload='test()'>\n"
232             + "</body>\n"
233             + "</html>";
234 
235         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
236         final HtmlPage page = loadPage(html, collectedAlerts);
237         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
238         assertNotNull(jobManager);
239         assertEquals(1, jobManager.getJobCount());
240 
241         startTimedTest();
242         assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(20_000));
243         assertMaxTestRunTime(1000);
244         assertEquals(0, jobManager.getJobCount());
245 
246         final String[] expectedAlerts = {"work1", "work2"};
247         assertEquals(expectedAlerts, collectedAlerts);
248     }
249 
250     /**
251      * When waitForBackgroundJavaScriptStartingBefore is called and a new job is scheduled after the one that
252      * is first found as the last one within the delay (a job starts a new one or simply a setInterval),
253      * a few retries should be done to see if new jobs exists.
254      * @throws Exception if the test fails
255      */
256     @Test
257     @Tries(3)
258     public void waitWithsubWindows() throws Exception {
259         final String html = DOCTYPE_HTML
260             + "<html>\n"
261             + "<head>\n"
262             + "  <title>test</title>\n"
263             + "</head>\n"
264             + "<body>\n"
265             + "<iframe src='nested.html'></iframe>\n"
266             + "</body>\n"
267             + "</html>";
268         final String nested = DOCTYPE_HTML
269             + "<html>\n"
270             + "<head>\n"
271             + "  <title>nested</title>\n"
272             + "  <script>\n"
273             + "    function test() {\n"
274             + "      setTimeout(doWork1, 200);\n"
275             + "    }\n"
276             + "    function doWork1() {\n"
277             + "      alert('work1');\n"
278             + "    }\n"
279             + "  </script>\n"
280             + "</head>\n"
281             + "<body onload='test()'>\n"
282             + "</body>\n"
283             + "</html>";
284 
285         final WebClient client = getWebClient();
286         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
287         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
288 
289         final MockWebConnection webConnection = new MockWebConnection();
290         webConnection.setResponse(URL_FIRST, html);
291         webConnection.setDefaultResponse(nested);
292 
293         client.setWebConnection(webConnection);
294 
295         final HtmlPage page = client.getPage(URL_FIRST);
296 
297         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
298         assertNotNull(jobManager);
299         assertEquals(0, jobManager.getJobCount());
300 
301         startTimedTest();
302         assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
303         assertMaxTestRunTime(300);
304         assertEquals(0, jobManager.getJobCount());
305 
306         final String[] expectedAlerts = {"work1"};
307         assertEquals(expectedAlerts, collectedAlerts);
308     }
309 
310     /**
311      * Test the case where a job is being executed at the time where waitForBackgroundJavaScriptStartingBefore
312      * and where this jobs schedules a new job after call to waitForBackgroundJavaScriptStartingBefore,
313      * .
314      * @throws Exception if the test fails
315      */
316     @Test
317     @Tries(3)
318     public void newJobStartedAfterWait() throws Exception {
319         final String html = DOCTYPE_HTML
320             + "<html>\n"
321             + "<head>\n"
322             + "  <title>test</title>\n"
323             + "  <script>\n"
324             + "    var request;\n"
325             + "    function onReadyStateChange() {\n"
326             + "      if (request.readyState == 4) {\n"
327             + "        alert('xhr onchange');\n"
328             + "        setTimeout(doWork1, 200);\n"
329             + "      }\n"
330             + "    }\n"
331             + "    function test() {\n"
332             + "      request = new XMLHttpRequest();\n"
333             + "      request.open('GET', 'wait', true);\n"
334             + "      request.onreadystatechange = onReadyStateChange;\n"
335             + "      // waitForBackgroundJavaScriptStartingBefore should be called when JS execution is in send()\n"
336             + "      request.send('');\n"
337             + "    }\n"
338             + "    function doWork1() {\n"
339             + "      alert('work1');\n"
340             + "    }\n"
341             + "  </script>\n"
342             + "</head>\n"
343             + "<body onload='test()'>\n"
344             + "</body>\n"
345             + "</html>";
346 
347         final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
348         final MockWebConnection webConnection = new MockWebConnection() {
349             @Override
350             public WebResponse getResponse(final WebRequest request) throws IOException {
351                 if (request.getUrl().toExternalForm().endsWith("/wait")) {
352                     threadSynchronizer.waitForState("just before waitForBackgroundJavaScriptStartingBefore");
353                     threadSynchronizer.sleep(400); // main thread need to be able to process next instruction
354                 }
355                 return super.getResponse(request);
356             }
357         };
358         webConnection.setResponse(URL_FIRST, html);
359         webConnection.setDefaultResponse("");
360 
361         final WebClient client = getWebClient();
362         client.setWebConnection(webConnection);
363 
364         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
365         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
366 
367         final HtmlPage page = client.getPage(URL_FIRST);
368         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
369         assertNotNull(jobManager);
370         assertEquals(1, jobManager.getJobCount());
371 
372         startTimedTest();
373         threadSynchronizer.setState("just before waitForBackgroundJavaScriptStartingBefore");
374         assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
375         assertMaxTestRunTime(1000);
376         assertEquals(0, jobManager.getJobCount());
377 
378         final String[] expectedAlerts = {"xhr onchange", "work1"};
379         assertEquals(expectedAlerts, collectedAlerts);
380     }
381 
382     /**
383      * Tests that waitForBackgroundJavaScriptStartingBefore waits for jobs that should have been started earlier
384      * but that are "late" due to processing of previous job.
385      * This test needs to start many setTimeout to expect to reach the state, where a check for future
386      * jobs occurs when one of this job is not active.
387      * @throws Exception if the test fails
388      */
389     @Test
390     @Tries(3)
391     public void waitForJobThatIsAlreadyLate() throws Exception {
392         final String html = DOCTYPE_HTML
393             + "<html>\n"
394             + "<head>\n"
395             + "  <script>\n"
396             + "    var counter = 0;\n"
397             + "    function test() {\n"
398             + "      setTimeout(doWork1, 0);\n"
399             + "    }\n"
400             + "    function doWork1() {\n"
401             + "      if (counter++ < 50) {\n"
402             + "        setTimeout(doWork1, 0);\n"
403             + "      }\n"
404             + "      alert('work1');\n"
405             + "    }\n"
406             + "  </script>\n"
407             + "</head>\n"
408             + "<body onload='test()'>\n"
409             + "</body>\n"
410             + "</html>";
411 
412         final MockWebConnection webConnection = new MockWebConnection();
413         webConnection.setResponse(URL_FIRST, html);
414         webConnection.setDefaultResponse("");
415 
416         final WebClient client = getWebClient();
417         client.setWebConnection(webConnection);
418 
419         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
420         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
421 
422         client.getPage(URL_FIRST);
423 
424         startTimedTest();
425         assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(1000));
426         assertMaxTestRunTime(1000);
427         assertEquals(51, collectedAlerts.size());
428     }
429 
430     /**
431      * {@link WebClient#waitForBackgroundJavaScript(long)} should have an overview of all windows.
432      * @throws Exception if the test fails
433      */
434     @Test
435     public void jobSchedulesJobInOtherWindow1() throws Exception {
436         final String html = DOCTYPE_HTML
437             + "<html>\n"
438             + "<head>\n"
439             + "  <script>\n"
440             + "    var counter = 0;\n"
441             + "    function test() {\n"
442             + "      var w = window.open('about:blank');\n"
443             + "      w.setTimeout(doWork1, 200);\n"
444             + "    }\n"
445             + "    function doWork1() {\n"
446             + "      alert('work1');\n"
447             + "      setTimeout(doWork2, 400);\n"
448             + "    }\n"
449             + "    function doWork2() {\n"
450             + "      alert('work2');\n"
451             + "    }\n"
452             + "  </script>\n"
453             + "</head>\n"
454             + "<body onload='test()'>\n"
455             + "</body>\n"
456             + "</html>";
457 
458         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
459         final HtmlPage page = loadPage(html, collectedAlerts);
460 
461         startTimedTest();
462         assertEquals(0, page.getWebClient().waitForBackgroundJavaScript(1000));
463         assertMaxTestRunTime(1000);
464 
465         final String[] expectedAlerts = {"work1", "work2"};
466         assertEquals(expectedAlerts, collectedAlerts);
467     }
468 
469     /**
470      * {@link WebClient#waitForBackgroundJavaScriptStartingBefore(long)} should have an overview of all windows.
471      * @throws Exception if the test fails
472      */
473     @Test
474     public void jobSchedulesJobInOtherWindow2() throws Exception {
475         final String html = DOCTYPE_HTML
476             + "<html>\n"
477             + "<head>\n"
478             + "  <script>\n"
479             + "    var counter = 0;\n"
480             + "    function test() {\n"
481             + "      var w = window.open('about:blank');\n"
482             + "      w.setTimeout(doWork1, 200);\n"
483             + "    }\n"
484             + "    function doWork1() {\n"
485             + "      alert('work1');\n"
486             + "      setTimeout(doWork2, 400);\n"
487             + "    }\n"
488             + "    function doWork2() {\n"
489             + "      alert('work2');\n"
490             + "    }\n"
491             + "  </script>\n"
492             + "</head>\n"
493             + "<body onload='test()'>\n"
494             + "</body>\n"
495             + "</html>";
496 
497         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
498         final HtmlPage page = loadPage(html, collectedAlerts);
499 
500         startTimedTest();
501         assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(1000));
502         assertMaxTestRunTime(1000);
503 
504         final String[] expectedAlerts = {"work1", "work2"};
505         assertEquals(expectedAlerts, collectedAlerts);
506     }
507 
508     /**
509      * HtmlUnit-2.7-SNAPSHOT (as of 29.10.09) had bug with
510      * WebClient.waitForBackgroundJavaScriptStartingBefore: it could be totally blocking
511      * under some circumstances. This test reproduces the problem but ensures
512      * that the test terminates (calling clearInterval when waitForBackgroundJavaScriptStartingBefore
513      * has not done its job correctly).
514      * @throws Exception if the test fails
515      */
516     @Test
517     @Tries(3)
518     public void waitForBackgroundJavaScriptStartingBefore_hangs() throws Exception {
519         final String html = DOCTYPE_HTML
520             + "<html>\n"
521             + "<head>\n"
522             + "  <title>test</title>\n"
523             + "  <script>\n"
524             + "    var start = new Date().getTime();\n"
525             + "    var id = setInterval(doWork1, 35);\n"
526             + "    function stopTimer() {\n"
527             + "      clearInterval(id);\n"
528             + "    }\n"
529             + "    function doWork1() {\n"
530             + "      if (start + 8000 < new Date().getTime()) {\n"
531             + "        document.title = 'failed';\n"
532             + "        clearInterval(id);\n"
533             + "      }\n"
534             + "    }\n"
535             + "  </script>\n"
536             + "</head>\n"
537             + "<body>\n"
538             + "<button onclick='stopTimer()'>stop</button>\n"
539             + "</body>\n"
540             + "</html>";
541 
542         final WebClient client = getWebClient();
543 
544         final MockWebConnection webConnection = new MockWebConnection();
545         webConnection.setDefaultResponse(html);
546         client.setWebConnection(webConnection);
547 
548         final HtmlPage page = client.getPage(URL_FIRST);
549 
550         int noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
551         assertTrue(noOfJobs == 1 || noOfJobs == 2); // maybe one is running
552 
553         assertEquals("test", page.getTitleText());
554         noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
555         assertTrue(noOfJobs == 1 || noOfJobs == 2); // maybe one is running
556     }
557 
558     /**
559      * Methods waitForBackgroundJavaScript[StartingBefore] should not look for running jobs only on the existing
560      * windows but as well on the ones that have been (freshly) closed.
561      * This test shows the case where a background job in a frame causes the window of this frame to be unregistered
562      * by the WebClient but this job should still be considered until it completes.
563      * @throws Exception if the test fails
564      */
565     @Test
566     public void jobsFromAClosedWindowShouldntBeIgnore() throws Exception {
567         final String html = DOCTYPE_HTML
568             + "<html><head><title>page 1</title></head>\n"
569             + "<body>\n"
570             + "<iframe src='iframe.html'></iframe>\n"
571             + "</body></html>";
572 
573         final String iframe = DOCTYPE_HTML
574                 + "<html><body>\n"
575                 + "<script>\n"
576                 + "setTimeout(function() { parent.location = '/page3.html'; }, 50);\n"
577                 + "</script>\n"
578                 + "</body></html>";
579         final String page3 = DOCTYPE_HTML
580                 + "<html><body><script>\n"
581                 + "parent.location = '/delayedPage4.html';\n"
582                 + "</script></body></html>";
583 
584         final WebClient client = getWebClient();
585 
586         final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
587 
588         final MockWebConnection webConnection = new MockWebConnection() {
589             @Override
590             public WebResponse getResponse(final WebRequest request) throws IOException {
591                 if (request.getUrl().toExternalForm().endsWith("/delayedPage4.html")) {
592                     threadSynchronizer.setState("/delayedPage4.html requested");
593                     threadSynchronizer.waitForState("ready to call waitForBGJS");
594                     threadSynchronizer.sleep(1000);
595                 }
596                 return super.getResponse(request);
597             }
598         };
599 
600         webConnection.setDefaultResponse(html);
601         webConnection.setResponse(new URL(URL_FIRST, "iframe.html"), iframe);
602         webConnection.setResponse(new URL(URL_FIRST, "page3.html"), page3);
603         webConnection.setResponseAsGenericHtml(new URL(URL_FIRST, "delayedPage4.html"), "page 4");
604         client.setWebConnection(webConnection);
605 
606         client.getPage(URL_FIRST);
607 
608         threadSynchronizer.waitForState("/delayedPage4.html requested");
609         threadSynchronizer.setState("ready to call waitForBGJS");
610         final int noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
611         assertEquals(0, noOfJobs);
612 
613         final HtmlPage page = (HtmlPage) client.getCurrentWindow().getEnclosedPage();
614         assertEquals("page 4", page.getTitleText());
615     }
616 }
617 
618 /**
619  * Helper to ensure some synchronization state between threads to reproduce a particular situation in the tests.
620  * @author Marc Guillemot
621  */
622 class ThreadSynchronizer {
623     private String state_ = "initial";
624     private static final Log LOG = LogFactory.getLog(ThreadSynchronizer.class);
625 
626     synchronized void setState(final String newState) {
627         state_ = newState;
628         notifyAll();
629     }
630 
631     /**
632      * Just like {@link Thread#sleep(long)} but throws a {@link RuntimeException}.
633      * @param millis the time to sleep in milliseconds
634      */
635     public void sleep(final long millis) {
636         try {
637             if (LOG.isDebugEnabled()) {
638                 LOG.debug("Sleeping for " + millis + "ms");
639             }
640             Thread.sleep(millis);
641         }
642         catch (final InterruptedException e) {
643             throw new RuntimeException(e);
644         }
645     }
646 
647     synchronized void waitForState(final String expectedState) {
648         try {
649             while (!state_.equals(expectedState)) {
650                 wait();
651             }
652         }
653         catch (final InterruptedException e) {
654             throw new RuntimeException(e);
655         }
656     }
657 }