2020 * #L%
2121 */
2222
23+ import static com .github .kklisura .cdtp .utils .ChromeDevToolsUtils .closeQuietly ;
24+
2325import com .github .kklisura .cdtp .launch .config .ChromeLauncherConfiguration ;
26+ import com .github .kklisura .cdtp .launch .exceptions .ChromeProcessException ;
27+ import com .github .kklisura .cdtp .launch .exceptions .ChromeProcessTimeoutException ;
2428import com .github .kklisura .cdtp .launch .support .ProcessLauncher ;
2529import com .github .kklisura .cdtp .launch .support .annotations .ChromeArgument ;
2630import com .github .kklisura .cdtp .launch .support .impl .ProcessLauncherImpl ;
2731import com .github .kklisura .cdtp .services .ChromeService ;
2832import com .github .kklisura .cdtp .services .impl .ChromeServiceImpl ;
2933import java .io .BufferedReader ;
30- import java .io .Closeable ;
3134import java .io .IOException ;
3235import java .io .InputStreamReader ;
3336import java .lang .reflect .Field ;
4043import java .util .concurrent .TimeUnit ;
4144import java .util .concurrent .atomic .AtomicBoolean ;
4245import java .util .concurrent .atomic .AtomicInteger ;
46+ import java .util .concurrent .atomic .AtomicReference ;
4347import java .util .regex .Matcher ;
4448import java .util .regex .Pattern ;
4549import org .slf4j .Logger ;
@@ -56,7 +60,7 @@ public class ChromeLauncher implements AutoCloseable {
5660
5761 private static final Logger LOGGER = LoggerFactory .getLogger (ChromeLauncher .class );
5862
59- private static final Pattern DEVTOOLS_LISTENING_LINE_MATCH =
63+ private static final Pattern DEVTOOLS_LISTENING_LINE_PATTERN =
6064 Pattern .compile ("^DevTools listening on ws:\\ /\\ /.+?:(\\ d+)\\ /" );
6165
6266 private static final String [] CHROME_BINARIES =
@@ -124,10 +128,12 @@ public ChromeLauncher(
124128 * @param chromeBinaryPath the chrome binary path
125129 * @param chromeArguments the chrome arguments
126130 * @return Chrome service.
127- * @throws IOException the io exception
131+ * @throws IllegalStateException If chrome process has already been started.
132+ * @throws ChromeProcessException If an I/O error occurs during chrome process start.
133+ * @throws ChromeProcessTimeoutException If timeout expired while waiting for chrome to start.
128134 */
129135 public ChromeService launch (Path chromeBinaryPath , ChromeArguments chromeArguments )
130- throws IOException {
136+ throws ChromeProcessException {
131137 int port = launchChromeProcess (chromeBinaryPath , chromeArguments );
132138 return new ChromeServiceImpl (port );
133139 }
@@ -137,9 +143,11 @@ public ChromeService launch(Path chromeBinaryPath, ChromeArguments chromeArgumen
137143 *
138144 * @param chromeArguments the chrome arguments
139145 * @return Chrome service.
140- * @throws IOException the io exception
146+ * @throws IllegalStateException If chrome process has already been started.
147+ * @throws ChromeProcessException If an I/O error occurs during chrome process start.
148+ * @throws ChromeProcessTimeoutException If timeout expired while waiting for chrome to start.
141149 */
142- public ChromeService launch (ChromeArguments chromeArguments ) throws IOException {
150+ public ChromeService launch (ChromeArguments chromeArguments ) throws ChromeProcessException {
143151 return launch (getChromeBinaryPath (), chromeArguments );
144152 }
145153
@@ -148,19 +156,23 @@ public ChromeService launch(ChromeArguments chromeArguments) throws IOException
148156 *
149157 * @param headless Headless flag.
150158 * @return Chrome service.
151- * @throws IOException the io exception
159+ * @throws IllegalStateException If chrome process has already been started.
160+ * @throws ChromeProcessException If an I/O error occurs during chrome process start.
161+ * @throws ChromeProcessTimeoutException If timeout expired while waiting for chrome to start.
152162 */
153- public ChromeService launch (boolean headless ) throws IOException {
163+ public ChromeService launch (boolean headless ) throws ChromeProcessException {
154164 return launch (getChromeBinaryPath (), ChromeArguments .defaults (headless ).build ());
155165 }
156166
157167 /**
158168 * Launches a headless chrome with default arguments.
159169 *
160170 * @return Chrome service.
161- * @throws IOException the io exception
171+ * @throws IllegalStateException If chrome process has already been started.
172+ * @throws ChromeProcessException If an I/O error occurs during chrome process start.
173+ * @throws ChromeProcessTimeoutException If timeout expired while waiting for chrome to start.
162174 */
163- public ChromeService launch () throws IOException {
175+ public ChromeService launch () throws ChromeProcessException {
164176 return launch (true );
165177 }
166178
@@ -177,6 +189,8 @@ public Path getChromeBinaryPath() {
177189 if (isExecutable ) {
178190 return Paths .get (envChrome ).toAbsolutePath ();
179191 }
192+
193+ throw new RuntimeException ("CHROME_PATH environment value is not an executable file." );
180194 }
181195
182196 for (String binary : CHROME_BINARIES ) {
@@ -220,8 +234,18 @@ public void close() {
220234 }
221235 }
222236
237+ /**
238+ * Launches a chrome process given a chrome binary and its arguments.
239+ *
240+ * @param chromeBinary Chrome binary path.
241+ * @param chromeArguments Chrome arguments.
242+ * @return Port on which devtools is listening.
243+ * @throws IllegalStateException If chrome process has already been started.
244+ * @throws ChromeProcessException If an I/O error occurs during chrome process start.
245+ * @throws ChromeProcessTimeoutException If timeout expired while waiting for chrome to start.
246+ */
223247 private int launchChromeProcess (Path chromeBinary , ChromeArguments chromeArguments )
224- throws IOException {
248+ throws ChromeProcessException {
225249 if (chromeProcess != null ) {
226250 throw new IllegalStateException ("Chrome process has already been started started." );
227251 }
@@ -235,34 +259,59 @@ private int launchChromeProcess(Path chromeBinary, ChromeArguments chromeArgumen
235259 LOGGER .info (
236260 "Launching chrome process {} with arguments {}" , chromeBinary .toString (), argumentsMap );
237261
238- chromeProcess = processLauncher .launch (chromeBinary .toString (), arguments );
239- return waitForDevToolsServer (chromeProcess );
262+ try {
263+ chromeProcess = processLauncher .launch (chromeBinary .toString (), arguments );
264+ return waitForDevToolsServer (chromeProcess );
265+ } catch (IOException e ) {
266+ // Unsubscribe from registry on exceptions.
267+ shutdownHookRegistry .remove (shutdownHookThread );
268+
269+ throw new ChromeProcessException ("Failed starting chrome process." , e );
270+ } catch (Exception e ) {
271+ close ();
272+ throw e ;
273+ }
240274 }
241275
242- private int waitForDevToolsServer (final Process process ) {
276+ /**
277+ * Waits for DevTools server is up on chrome process.
278+ *
279+ * @param process Chrome process.
280+ * @return DevTools listening port.
281+ * @throws ChromeProcessTimeoutException If timeout expired while waiting for chrome process.
282+ */
283+ private int waitForDevToolsServer (final Process process ) throws ChromeProcessTimeoutException {
243284 final AtomicInteger port = new AtomicInteger ();
244285 final AtomicBoolean success = new AtomicBoolean (false );
286+ final AtomicReference <String > chromeOutput = new AtomicReference <>("" );
245287
246288 Thread readLineThread =
247289 new Thread (
248290 () -> {
291+ StringBuilder chromeOutputBuilder = new StringBuilder ();
249292 BufferedReader reader = null ;
250293 try {
251294 reader = new BufferedReader (new InputStreamReader (process .getInputStream ()));
252295
253296 // Wait for DevTools listening line and extract port number.
254297 String line ;
255298 while ((line = reader .readLine ()) != null ) {
256- Matcher matcher = DEVTOOLS_LISTENING_LINE_MATCH .matcher (line );
299+ Matcher matcher = DEVTOOLS_LISTENING_LINE_PATTERN .matcher (line );
257300 if (matcher .find ()) {
258301 port .set (Integer .parseInt (matcher .group (1 )));
259302 success .set (true );
260303 break ;
261304 }
305+
306+ if (chromeOutputBuilder .length () != 0 ) {
307+ chromeOutputBuilder .append (System .lineSeparator ());
308+ }
309+ chromeOutputBuilder .append (line );
262310 }
263311 } catch (Exception e ) {
264312 LOGGER .error ("Failed while waiting for dev tools server." , e );
265313 } finally {
314+ chromeOutput .set (chromeOutputBuilder .toString ());
266315 closeQuietly (reader );
267316 }
268317 });
@@ -274,7 +323,11 @@ private int waitForDevToolsServer(final Process process) {
274323
275324 if (!success .get ()) {
276325 close (readLineThread );
277- throw new RuntimeException ("Failed while waiting for chrome to start. Timeout expired!" );
326+
327+ throw new ChromeProcessTimeoutException (
328+ "Failed while waiting for chrome to start: "
329+ + "Timeout expired! Chrome output: "
330+ + chromeOutput .get ());
278331 }
279332 } catch (InterruptedException e ) {
280333 close (readLineThread );
@@ -287,7 +340,6 @@ private int waitForDevToolsServer(final Process process) {
287340 }
288341
289342 private void close (Thread thread ) {
290- close ();
291343 try {
292344 thread .join (TimeUnit .SECONDS .toMillis (configuration .getThreadWaitTime ()));
293345 } catch (InterruptedException e ) {
@@ -332,16 +384,6 @@ private Map<String, Object> getArguments(ChromeArguments arguments) {
332384 return args ;
333385 }
334386
335- private static void closeQuietly (Closeable closeable ) {
336- if (closeable != null ) {
337- try {
338- closeable .close ();
339- } catch (IOException e ) {
340- // Ignore this exception.
341- }
342- }
343- }
344-
345387 /** Environment interface. */
346388 @ FunctionalInterface
347389 public interface Environment {
0 commit comments