Skip to content

Commit c39a177

Browse files
committed
debug macos failure
Signed-off-by: Andrei Gherghescu <[email protected]>
1 parent de1336b commit c39a177

File tree

4 files changed

+260
-29
lines changed

4 files changed

+260
-29
lines changed

.github/workflows/ci.yml

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,34 +137,82 @@ jobs:
137137

138138
- name: Test Chrome and chromedriver installation
139139
run: |
140-
echo "Checking Chrome installation..."
140+
echo "=== System Information ==="
141+
echo "macOS version: $(sw_vers)"
142+
echo "Architecture: $(uname -m)"
143+
echo "PATH: $PATH"
144+
echo "Current user: $(whoami)"
145+
echo "Home directory: $HOME"
146+
147+
echo "=== Chrome Installation Check ==="
141148
ls -la "/Applications/Google Chrome.app/Contents/MacOS/" || echo "Chrome not found in Applications"
142149
which google-chrome || echo "Chrome not found in PATH"
150+
google-chrome --version || echo "Chrome version check failed"
143151
144-
echo "Checking chromedriver installation..."
152+
echo "=== chromedriver Installation Check ==="
145153
chromedriver --version || echo "chromedriver not found in PATH"
146154
ls -la /usr/local/bin/chromedriver || echo "chromedriver not found in /usr/local/bin"
147155
ls -la /opt/homebrew/bin/chromedriver || echo "chromedriver not found in /opt/homebrew/bin"
156+
157+
echo "=== Environment Variables ==="
158+
echo "WEBDRIVER_PATH: ${WEBDRIVER_PATH:-not set}"
159+
echo "CHROMEDRIVER_PATH: ${CHROMEDRIVER_PATH:-not set}"
160+
echo "CHROME_PATH: ${CHROME_PATH:-not set}"
148161
149162
- name: Run macOS-specific tests
150163
run: |
151164
echo "Running macOS compatibility tests..."
152-
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_chrome_installation
153-
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_chromedriver_installation
154-
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_static_exporter_creation_macos
155-
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_basic_png_export_macos
156-
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_macos_chrome_flags
157-
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_user_data_directory_management
165+
echo "Current directory: $(pwd)"
166+
echo "Rust version: $(rustc --version)"
167+
echo "Cargo version: $(cargo --version)"
168+
169+
# Run tests with verbose output and capture failures
170+
set -x # Enable debug mode
171+
172+
echo "=== Testing Chrome installation ==="
173+
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_chrome_installation -- --nocapture || echo "Chrome installation test failed"
174+
175+
echo "=== Testing chromedriver installation ==="
176+
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_chromedriver_installation -- --nocapture || echo "chromedriver installation test failed"
177+
178+
echo "=== Testing static exporter creation ==="
179+
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_static_exporter_creation_macos -- --nocapture || echo "Static exporter creation test failed"
180+
181+
echo "=== Testing basic PNG export ==="
182+
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_basic_png_export_macos -- --nocapture || echo "Basic PNG export test failed"
183+
184+
echo "=== Testing macOS Chrome flags ==="
185+
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_macos_chrome_flags -- --nocapture || echo "macOS Chrome flags test failed"
186+
187+
echo "=== Testing user data directory management ==="
188+
cargo test -p plotly_static --features chromedriver,webdriver_download --lib macos_tests::macos_tests::test_user_data_directory_management -- --nocapture || echo "User data directory management test failed"
158189
159190
- name: Run all plotly_static tests on macOS
160191
run: |
161192
echo "Running all plotly_static tests on macOS..."
162-
cargo test -p plotly_static --features chromedriver,webdriver_download --lib
193+
echo "Checking for existing chromedriver processes before tests..."
194+
ps aux | grep chromedriver || echo "No chromedriver processes found"
195+
196+
# Run tests with timeout to prevent hanging
197+
timeout 300 cargo test -p plotly_static --features chromedriver,webdriver_download --lib -- --nocapture || echo "Tests timed out or failed"
198+
199+
echo "Checking for chromedriver processes after tests..."
200+
ps aux | grep chromedriver || echo "No chromedriver processes found"
163201
164202
- name: Test plotly with static export on macOS
165203
run: |
166204
echo "Testing plotly with static export on macOS..."
167-
cargo test -p plotly --features static_export_chromedriver,static_export_downloader --lib
205+
echo "Checking for port conflicts before tests..."
206+
lsof -i :4444 || echo "Port 4444 is free"
207+
lsof -i :4445 || echo "Port 4445 is free"
208+
lsof -i :4446 || echo "Port 4446 is free"
209+
lsof -i :4447 || echo "Port 4447 is free"
210+
211+
cargo test -p plotly --features static_export_chromedriver,static_export_downloader --lib -- --nocapture || echo "plotly static export tests failed"
212+
213+
echo "Cleaning up after tests..."
214+
pkill -f chromedriver || echo "No chromedriver processes to kill"
215+
pkill -f "Google Chrome" || echo "No Chrome processes to kill"
168216
169217
code-coverage:
170218
name: Code Coverage

plotly_static/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,19 @@ impl StaticExporter {
10121012

10131013
Ok(caps)
10141014
}
1015+
1016+
/// Get diagnostic information about the underlying WebDriver process
1017+
///
1018+
/// This method provides detailed information about the WebDriver process
1019+
/// for debugging purposes, including process status, port information,
1020+
/// and connection details.
1021+
///
1022+
/// # Returns
1023+
///
1024+
/// Returns a string with diagnostic information
1025+
pub fn get_webdriver_diagnostics(&self) -> String {
1026+
self.webdriver.get_diagnostics()
1027+
}
10151028
}
10161029

10171030
#[cfg(test)]

plotly_static/src/macos_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ mod macos_tests {
156156
println!("Successfully exported PNG on macOS ({} bytes)", data.len());
157157
}
158158
Err(e) => {
159-
panic!("Failed to export PNG on macOS: {:?}", e);
159+
let diagnostics = exporter.get_webdriver_diagnostics();
160+
panic!("Failed to export PNG on macOS: {:?}\n\nWebDriver Diagnostics:\n{}", e, diagnostics);
160161
}
161162
}
162163
}

plotly_static/src/webdriver.rs

Lines changed: 187 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -112,41 +112,116 @@ impl WebDriver {
112112
/// The spawned process will be automatically managed and can be stopped
113113
/// using the `stop()` method.
114114
pub(crate) fn spawn_webdriver(&mut self) {
115-
info!("Spawning {WEBDRIVER_BIN}");
115+
let port = self.inner.lock().unwrap().webdriver_port;
116+
let driver_path = self.inner.lock().unwrap().driver_path.clone();
117+
118+
info!("Spawning {WEBDRIVER_BIN} on port {} with path: {:?}", port, driver_path);
119+
120+
// Check if port is already in use
121+
if Self::is_webdriver_running(port) {
122+
warn!("WebDriver already running on port {}, attempting to connect instead", port);
123+
return;
124+
}
125+
126+
// Check if the driver binary exists and is executable
127+
if !driver_path.exists() {
128+
error!("WebDriver binary does not exist: {:?}", driver_path);
129+
return;
130+
}
116131

117132
// Spawn the process in the main thread to get the Child handle
118-
let mut command = Command::new(self.inner.lock().unwrap().driver_path.clone());
133+
let mut command = Command::new(&driver_path);
134+
command
135+
.arg(format!("--port={}", port));
136+
137+
// Add verbose flag only for chromedriver (geckodriver doesn't support it)
138+
#[cfg(feature = "chromedriver")]
139+
{
140+
command.arg("--verbose"); // Enable verbose logging for debugging
141+
}
142+
119143
command
120-
.arg(format!("--port={}", self.inner.lock().unwrap().webdriver_port))
121144
.stdin(Stdio::piped())
122145
.stdout(Stdio::piped())
123146
.stderr(Stdio::piped());
124147

148+
info!("Executing command: {:?} {:?}", command.get_program(), command.get_args());
149+
125150
let mut child = match command.spawn() {
126-
Ok(c) => c,
151+
Ok(c) => {
152+
info!("Successfully spawned {WEBDRIVER_BIN} with PID: {}", c.id());
153+
c
154+
}
127155
Err(e) => {
128156
error!("Failed to spawn '{WEBDRIVER_BIN}': {e}");
157+
error!("Command was: {:?} {:?}", command.get_program(), command.get_args());
129158
return;
130159
}
131160
};
132161

133162
// Spawn a thread to handle the process stderr output (logging only)
134163
if let Some(stderr) = child.stderr.take() {
164+
let port_for_logging = port;
135165
thread::spawn(move || {
166+
info!("Starting stderr monitoring for WebDriver on port {}", port_for_logging);
136167
let stderr_lines = BufReader::new(stderr).lines();
137168
for line in stderr_lines {
138169
if let Ok(line) = line {
139-
debug!("{line}");
170+
debug!("WebDriver[{}] stderr: {}", port_for_logging, line);
171+
}
172+
}
173+
info!("Stderr monitoring ended for WebDriver on port {}", port_for_logging);
174+
});
175+
}
176+
177+
// Spawn a thread to handle the process stdout output (logging only)
178+
if let Some(stdout) = child.stdout.take() {
179+
let port_for_logging = port;
180+
thread::spawn(move || {
181+
info!("Starting stdout monitoring for WebDriver on port {}", port_for_logging);
182+
let stdout_lines = BufReader::new(stdout).lines();
183+
for line in stdout_lines {
184+
if let Ok(line) = line {
185+
debug!("WebDriver[{}] stdout: {}", port_for_logging, line);
140186
}
141187
}
188+
info!("Stdout monitoring ended for WebDriver on port {}", port_for_logging);
142189
});
143190
}
144191

145-
// Store the Child handle (without stderr)
192+
// Store the Child handle (without stderr/stdout)
146193
{
147194
let mut inner = self.inner.lock().unwrap();
148195
inner.webdriver_child = Some(child);
149196
}
197+
198+
info!("WebDriver process stored, waiting for it to become ready...");
199+
200+
// Wait for WebDriver to become ready with timeout
201+
let start_time = std::time::Instant::now();
202+
let timeout_duration = std::time::Duration::from_secs(30);
203+
204+
while start_time.elapsed() < timeout_duration {
205+
if Self::is_webdriver_running(port) {
206+
info!("WebDriver is ready on port {} after {:?}", port, start_time.elapsed());
207+
return;
208+
}
209+
210+
// Check if process is still alive
211+
{
212+
let mut inner = self.inner.lock().unwrap();
213+
if let Some(child) = inner.webdriver_child.as_mut() {
214+
if let Ok(Some(_)) = child.try_wait() {
215+
error!("WebDriver process exited before becoming ready on port {}", port);
216+
return;
217+
}
218+
}
219+
}
220+
221+
std::thread::sleep(std::time::Duration::from_millis(100));
222+
}
223+
224+
error!("WebDriver failed to become ready on port {} within {:?}", port, timeout_duration);
150225
}
151226

152227
/// Stops the WebDriver process
@@ -193,6 +268,47 @@ impl WebDriver {
193268
Ok(())
194269
}
195270

271+
/// Get diagnostic information about the WebDriver process
272+
///
273+
/// This method provides detailed information about the WebDriver process
274+
/// for debugging purposes.
275+
///
276+
/// # Returns
277+
///
278+
/// Returns a string with diagnostic information
279+
pub fn get_diagnostics(&self) -> String {
280+
let mut inner = self.inner.lock().unwrap();
281+
let mut diagnostics = String::new();
282+
283+
diagnostics.push_str(&format!("WebDriver Diagnostics:\n"));
284+
diagnostics.push_str(&format!(" Port: {}\n", inner.webdriver_port));
285+
diagnostics.push_str(&format!(" Driver Path: {:?}\n", inner.driver_path));
286+
diagnostics.push_str(&format!(" Is External: {}\n", inner.is_external));
287+
288+
if let Some(child) = inner.webdriver_child.as_mut() {
289+
diagnostics.push_str(&format!(" Process ID: {}\n", child.id()));
290+
291+
// Check if process is still running
292+
match child.try_wait() {
293+
Ok(None) => diagnostics.push_str(" Process Status: Running\n"),
294+
Ok(Some(status)) => diagnostics.push_str(&format!(" Process Status: Exited with {:?}\n", status)),
295+
Err(e) => diagnostics.push_str(&format!(" Process Status: Error checking status: {}\n", e)),
296+
}
297+
} else {
298+
diagnostics.push_str(" Process ID: None (no child process)\n");
299+
}
300+
301+
// Check if WebDriver is responding
302+
let is_running = Self::is_webdriver_running(inner.webdriver_port);
303+
diagnostics.push_str(&format!(" WebDriver Responding: {}\n", is_running));
304+
305+
// Check port availability
306+
let url = format!("{WEBDRIVER_URL}:{}/status", inner.webdriver_port);
307+
diagnostics.push_str(&format!(" Status URL: {}\n", url));
308+
309+
diagnostics
310+
}
311+
196312
/// Create a new WebDriver instance, connecting to existing if available
197313
///
198314
/// This method provides WebDriver management:
@@ -213,14 +329,44 @@ impl WebDriver {
213329
/// - `Ok(webdriver)` - Successfully created or connected to WebDriver
214330
/// - `Err(e)` - Failed to create or connect to WebDriver
215331
pub(crate) fn new_or_connect(port: u32) -> Result<Self> {
332+
info!("Attempting to create or connect to WebDriver on port {}", port);
333+
334+
// Check if port is already in use by another process
335+
debug!("Checking if port {} is available...", port);
336+
216337
if Self::is_webdriver_running(port) {
217-
debug!("WebDriver already running on port {port}, connecting to existing session");
218-
Self::connect_to_existing(port)
338+
info!("WebDriver already running on port {port}, connecting to existing session");
339+
match Self::connect_to_existing(port) {
340+
Ok(wd) => {
341+
info!("Successfully connected to existing WebDriver on port {}", port);
342+
Ok(wd)
343+
}
344+
Err(e) => {
345+
error!("Failed to connect to existing WebDriver on port {}: {}", port, e);
346+
Err(e)
347+
}
348+
}
219349
} else {
220-
debug!("No WebDriver running on port {port}, creating new instance and spawning");
221-
let mut wd = Self::new(port)?;
222-
wd.spawn_webdriver();
223-
Ok(wd)
350+
info!("No WebDriver running on port {port}, creating new instance and spawning");
351+
match Self::new(port) {
352+
Ok(mut wd) => {
353+
wd.spawn_webdriver();
354+
355+
// Verify that the WebDriver is now running
356+
if Self::is_webdriver_running(port) {
357+
info!("Successfully created and started WebDriver on port {}", port);
358+
Ok(wd)
359+
} else {
360+
let diagnostics = wd.get_diagnostics();
361+
error!("WebDriver failed to start properly on port {}. Diagnostics:\n{}", port, diagnostics);
362+
Err(anyhow!("WebDriver failed to start properly on port {}", port))
363+
}
364+
}
365+
Err(e) => {
366+
error!("Failed to create WebDriver instance on port {}: {}", port, e);
367+
Err(e)
368+
}
369+
}
224370
}
225371
}
226372

@@ -241,12 +387,35 @@ impl WebDriver {
241387
/// Returns `true` if WebDriver is running and ready, `false` otherwise.
242388
pub(crate) fn is_webdriver_running(port: u32) -> bool {
243389
let url = format!("{WEBDRIVER_URL}:{port}/status");
244-
reqwest::blocking::get(&url)
245-
.ok()
246-
.filter(|response| response.status().as_u16() == 200)
247-
.and_then(|response| response.text().ok())
248-
.map(|text| text.contains("ready"))
249-
.unwrap_or(false)
390+
391+
debug!("Checking WebDriver status at: {}", url);
392+
393+
match reqwest::blocking::get(&url) {
394+
Ok(response) => {
395+
debug!("WebDriver status response: {} {}", response.status(), response.status().as_u16());
396+
if response.status().as_u16() == 200 {
397+
match response.text() {
398+
Ok(text) => {
399+
debug!("WebDriver status response body: {}", text);
400+
let is_ready = text.contains("ready");
401+
debug!("WebDriver ready: {}", is_ready);
402+
is_ready
403+
}
404+
Err(e) => {
405+
debug!("Failed to read WebDriver status response: {}", e);
406+
false
407+
}
408+
}
409+
} else {
410+
debug!("WebDriver status returned non-200 status: {}", response.status());
411+
false
412+
}
413+
}
414+
Err(e) => {
415+
debug!("Failed to connect to WebDriver at {}: {}", url, e);
416+
false
417+
}
418+
}
250419
}
251420

252421
/// Connect to an existing WebDriver session

0 commit comments

Comments
 (0)