Skip to content

Commit 6234c66

Browse files
authored
fail faster on errors reading CockroachDB listening-url file (#2091)
1 parent 83c6572 commit 6234c66

File tree

1 file changed

+82
-7
lines changed

1 file changed

+82
-7
lines changed

test-utils/src/dev/db.rs

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@ impl CockroachStarter {
315315
self.temp_dir.path()
316316
}
317317

318+
/// Returns the path to the listen-url file for this execution
319+
#[cfg(test)]
320+
pub fn listen_url_file(&self) -> &Path {
321+
&self.listen_url_file
322+
}
323+
318324
/// Returns the path to the storage directory created for this execution.
319325
pub fn store_dir(&self) -> &Path {
320326
self.store_dir.as_path()
@@ -367,6 +373,8 @@ impl CockroachStarter {
367373
// memory.
368374
match tokio::fs::read_to_string(&listen_url_file).await {
369375
Ok(listen_url) if listen_url.contains('\n') => {
376+
// The file is fully written.
377+
// We're ready to move on.
370378
let listen_url = listen_url.trim_end();
371379
make_pg_config(listen_url).map_err(|source| {
372380
poll::CondCheckError::Failed(
@@ -378,7 +386,31 @@ impl CockroachStarter {
378386
})
379387
}
380388

381-
_ => Err(poll::CondCheckError::NotYet),
389+
Ok(_) => {
390+
// The file hasn't been fully written yet.
391+
// Keep waiting.
392+
Err(poll::CondCheckError::NotYet)
393+
}
394+
395+
Err(error)
396+
if error.kind() == std::io::ErrorKind::NotFound =>
397+
{
398+
// The file doesn't exist yet.
399+
// Keep waiting.
400+
Err(poll::CondCheckError::NotYet)
401+
}
402+
403+
Err(error) => {
404+
// Something else has gone wrong. Stop immediately
405+
// and report the problem.
406+
let source = anyhow!(error).context(format!(
407+
"checking listen file {:?}",
408+
listen_url_file
409+
));
410+
Err(poll::CondCheckError::Failed(
411+
CockroachStartError::Unknown { source },
412+
))
413+
}
382414
}
383415
}
384416
},
@@ -996,7 +1028,7 @@ mod test {
9961028
#[tokio::test]
9971029
async fn test_bad_cmd() {
9981030
let builder = CockroachStarterBuilder::new_with_cmd("/nonexistent");
999-
let _ = test_database_start_failure(builder).await;
1031+
let _ = test_database_start_failure(builder.build().unwrap()).await;
10001032
}
10011033

10021034
// Tests what happens if the "cockroach" command exits before writing the
@@ -1006,7 +1038,8 @@ mod test {
10061038
async fn test_cmd_fails() {
10071039
let mut builder = new_builder();
10081040
builder.arg("not-a-valid-argument");
1009-
let temp_dir = test_database_start_failure(builder).await;
1041+
let (temp_dir, _) =
1042+
test_database_start_failure(builder.build().unwrap()).await;
10101043
fs::metadata(&temp_dir).await.expect("temporary directory was deleted");
10111044
// The temporary directory is preserved in this case so that we can
10121045
// debug the failure. In this case, we injected the failure. Remove
@@ -1032,9 +1065,8 @@ mod test {
10321065
// caller can decide whether to check if it was cleaned up or not. The
10331066
// expected behavior depends on the failure mode.
10341067
async fn test_database_start_failure(
1035-
builder: CockroachStarterBuilder,
1036-
) -> PathBuf {
1037-
let starter = builder.build().unwrap();
1068+
starter: CockroachStarter,
1069+
) -> (PathBuf, CockroachStartError) {
10381070
let temp_dir = starter.temp_dir().to_owned();
10391071
eprintln!("will run: {}", starter.cmdline());
10401072
eprintln!("environment:");
@@ -1044,7 +1076,7 @@ mod test {
10441076
let error =
10451077
starter.start().await.expect_err("unexpectedly started database");
10461078
eprintln!("error: {:?}", error);
1047-
temp_dir
1079+
(temp_dir, error)
10481080
}
10491081

10501082
// Tests when CockroachDB hangs on startup by setting the start timeout
@@ -1132,6 +1164,49 @@ mod test {
11321164
});
11331165
}
11341166

1167+
// Test what happens if we can't read the listen-url file. This is a little
1168+
// obscure, but it has been a problem.
1169+
#[tokio::test]
1170+
async fn test_setup_database_bad_listen_url() {
1171+
// We don't need to actually run Cockroach for this test, and it's
1172+
// simpler (and faster) if we don't. But we do need something that
1173+
// won't exit before we get a chance to trigger an error and that can
1174+
// also accept the extra arguments that the builder will provide.
1175+
let mut builder = CockroachStarterBuilder::new_with_cmd("bash");
1176+
builder.arg("-c").arg("sleep 60");
1177+
let starter = builder.build().unwrap();
1178+
1179+
// We want to inject an error into the code path that reads the
1180+
// listen-url file. We do this by precreating that path as a directory.
1181+
// Then we'll get EISDIR when we try to read it.
1182+
let listen_url_file = starter.listen_url_file().to_owned();
1183+
std::fs::create_dir(&listen_url_file)
1184+
.expect("pre-creating listen-URL path as directory");
1185+
let (temp_dir, error) = test_database_start_failure(starter).await;
1186+
1187+
if let CockroachStartError::Unknown { source } = error {
1188+
let message = format!("{:#}", source);
1189+
eprintln!("error message was: {}", message);
1190+
// Verify the error message refers to the listening file (since
1191+
// that's what we were operating on) and also reflects the EISDIR
1192+
// error.
1193+
assert!(message.starts_with("checking listen file \""));
1194+
assert!(message.contains("Is a directory"));
1195+
} else {
1196+
panic!("unexpected error trying to start database: {:#}", error);
1197+
}
1198+
1199+
// Clean up the temporary directory -- carefully. Since we know exactly
1200+
// what should be in it, we opt to remove these items individually
1201+
// rather than risk blowing away something else inadvertently.
1202+
fs::remove_dir(&listen_url_file)
1203+
.await
1204+
.expect("failed to remove listen-url directory");
1205+
fs::remove_dir(temp_dir)
1206+
.await
1207+
.expect("failed to remove temporary directory");
1208+
}
1209+
11351210
// Test the happy path using the default store directory.
11361211
#[tokio::test]
11371212
async fn test_setup_database_default_dir() {

0 commit comments

Comments
 (0)