Skip to content

Commit 9c0749a

Browse files
authored
[dropshot-api-manager] write output to a writer to make rendering testable (#92)
In upcoming work we're going to change some of this rendering. Make it testable by writing to a `&mut dyn io::Write`, and add an expectorate snapshot test.
1 parent a19d6d0 commit 9c0749a

13 files changed

Lines changed: 474 additions & 164 deletions

File tree

Cargo.lock

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dropshot = "0.17.0"
3030
dropshot-api-manager = { path = "crates/dropshot-api-manager", version = "0.7.1" }
3131
dropshot-api-manager-types = { path = "crates/dropshot-api-manager-types", version = "0.7.1" }
3232
e2e-example-apis = { path = "e2e-example/apis" }
33+
expectorate = "1.2.0"
3334
fs-err = "3.1.1"
3435
git-stub = "1.0.0"
3536
git-stub-vcs = "0.1.0"

crates/dropshot-api-manager/src/cmd/check.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,74 @@ use crate::{
44
apis::ManagedApis,
55
environment::{BlessedSource, GeneratedSource, ResolvedEnv},
66
output::{
7-
CheckResult, OutputOpts, display_load_problems, display_resolution,
7+
CheckResult, Styles, display_load_problems, display_resolution,
88
headers::*,
99
},
1010
resolved::{ProblemSummary, Resolved},
1111
};
12+
use std::io;
1213

1314
pub(crate) fn check_impl(
15+
writer: &mut dyn io::Write,
1416
apis: &ManagedApis,
1517
env: &ResolvedEnv,
1618
blessed_source: &BlessedSource,
1719
generated_source: &GeneratedSource,
18-
output: &OutputOpts,
20+
styles: &Styles,
1921
) -> anyhow::Result<CheckResult> {
2022
let (result, _summaries) = check_impl_with_summaries(
23+
writer,
2124
apis,
2225
env,
2326
blessed_source,
2427
generated_source,
25-
output,
28+
styles,
2629
)?;
2730
Ok(result)
2831
}
2932

33+
/// Run the check pipeline, rendering all user-visible output to `writer`.
34+
///
35+
/// Production callers (the CLI dispatch path) pass `&mut
36+
/// std::io::stderr().lock()` along with [`OutputOpts::styles`]; tests pass a
37+
/// `Vec<u8>` and `Styles::default()` to capture the same output into a
38+
/// `String`.
3039
pub(crate) fn check_impl_with_summaries(
40+
writer: &mut dyn io::Write,
3141
apis: &ManagedApis,
3242
env: &ResolvedEnv,
3343
blessed_source: &BlessedSource,
3444
generated_source: &GeneratedSource,
35-
output: &OutputOpts,
45+
styles: &Styles,
3646
) -> anyhow::Result<(CheckResult, Vec<ProblemSummary>)> {
37-
let styles = output.styles(supports_color::Stream::Stderr);
47+
writeln!(writer, "{:>HEADER_WIDTH$}", SEPARATOR)?;
3848

39-
eprintln!("{:>HEADER_WIDTH$}", SEPARATOR);
40-
41-
let (generated, errors) =
42-
generated_source.load(apis, &styles, &env.repo_root, &env.vcs)?;
43-
display_load_problems(&errors, &styles)?;
49+
let (generated, errors) = generated_source.load(
50+
writer,
51+
apis,
52+
styles,
53+
&env.repo_root,
54+
&env.vcs,
55+
)?;
56+
display_load_problems(writer, &errors, styles)?;
4457

45-
let (local_files, errors) =
46-
env.local_source.load(apis, &styles, &env.repo_root, &env.vcs)?;
47-
display_load_problems(&errors, &styles)?;
58+
let (local_files, errors) = env.local_source.load(
59+
writer,
60+
apis,
61+
styles,
62+
&env.repo_root,
63+
&env.vcs,
64+
)?;
65+
display_load_problems(writer, &errors, styles)?;
4866

4967
let (blessed, errors) =
50-
blessed_source.load(&env.repo_root, apis, &styles, &env.vcs)?;
51-
display_load_problems(&errors, &styles)?;
68+
blessed_source.load(writer, &env.repo_root, apis, styles, &env.vcs)?;
69+
display_load_problems(writer, &errors, styles)?;
5270

5371
let resolved = Resolved::new(env, apis, &blessed, &generated, &local_files);
5472

55-
eprintln!("{:>HEADER_WIDTH$}", SEPARATOR);
56-
let result = display_resolution(env, apis, &resolved, &styles)?;
73+
writeln!(writer, "{:>HEADER_WIDTH$}", SEPARATOR)?;
74+
let result = display_resolution(writer, env, apis, &resolved, styles)?;
5775

5876
// Extract owned summaries before dropping the borrowed resolved state.
5977
let summaries = resolved.problem_summaries();

crates/dropshot-api-manager/src/cmd/debug.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,37 @@ pub(crate) fn debug_impl(
2020
output: &OutputOpts,
2121
) -> anyhow::Result<()> {
2222
let styles = output.styles(supports_color::Stream::Stderr);
23+
let mut stderr = std::io::stderr().lock();
2324

2425
// Print information about local files.
2526

26-
let (local_files, errors) =
27-
env.local_source.load(apis, &styles, &env.repo_root, &env.vcs)?;
27+
let (local_files, errors) = env.local_source.load(
28+
&mut stderr,
29+
apis,
30+
&styles,
31+
&env.repo_root,
32+
&env.vcs,
33+
)?;
2834
dump_structure(&local_files, &errors);
2935

3036
// Print information about what we found in VCS history.
31-
let (blessed, errors) =
32-
blessed_source.load(&env.repo_root, apis, &styles, &env.vcs)?;
37+
let (blessed, errors) = blessed_source.load(
38+
&mut stderr,
39+
&env.repo_root,
40+
apis,
41+
&styles,
42+
&env.vcs,
43+
)?;
3344
dump_structure(&blessed, &errors);
3445

3546
// Print information about generated files.
36-
let (generated, errors) =
37-
generated_source.load(apis, &styles, &env.repo_root, &env.vcs)?;
47+
let (generated, errors) = generated_source.load(
48+
&mut stderr,
49+
apis,
50+
&styles,
51+
&env.repo_root,
52+
&env.vcs,
53+
)?;
3854
dump_structure(&generated, &errors);
3955

4056
// Print result of resolving the differences.

crates/dropshot-api-manager/src/cmd/dispatch.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
use anyhow::Result;
1414
use camino::Utf8PathBuf;
1515
use clap::{Args, Parser, Subcommand};
16-
use std::process::ExitCode;
16+
use std::{io::Write as _, process::ExitCode};
1717

1818
/// Manage OpenAPI documents for this repository.
1919
///
@@ -41,7 +41,13 @@ impl App {
4141
match result {
4242
Ok(exit_code) => exit_code,
4343
Err(error) => {
44-
eprintln!("failure: {:#}", error);
44+
// Best-effort: if even the error report fails to write,
45+
// we still want to exit with FAILURE.
46+
let _ = writeln!(
47+
&mut std::io::stderr().lock(),
48+
"failure: {:#}",
49+
error,
50+
);
4551
ExitCode::FAILURE
4652
}
4753
}
@@ -300,8 +306,16 @@ impl CheckArgs {
300306
let env = env.resolve(self.local.dir)?;
301307
let blessed_source = self.blessed.to_blessed_source(&env)?;
302308
let generated_source = GeneratedSource::from(self.generated);
303-
Ok(check_impl(apis, &env, &blessed_source, &generated_source, output)?
304-
.to_exit_code())
309+
let styles = output.styles(supports_color::Stream::Stderr);
310+
Ok(check_impl(
311+
&mut std::io::stderr().lock(),
312+
apis,
313+
&env,
314+
&blessed_source,
315+
&generated_source,
316+
&styles,
317+
)?
318+
.to_exit_code())
305319
}
306320
}
307321

0 commit comments

Comments
 (0)