expectation_harness/
lib.rs1pub mod bindings;
13pub mod driver;
14pub mod run;
15pub use expectation_service as service;
17
18pub use bindings::Bindings;
19pub use driver::{Driver, Need, Progress, Screenshot};
20pub use run::{RunOptions, run};
21
22use expectation_docs::Docs;
23use expectation_graph::Analysis;
24use expectation_language::Loaded;
25use expectation_model::diagnostic::{has_errors, render_all};
26use expectation_model::{Diagnostic, Suite};
27use expectation_story::{Plan, Report};
28use std::path::Path;
29
30pub struct Acceptance {
32 pub suite: Suite,
33 pub analysis: Analysis,
34 pub plans: Vec<Plan>,
35 pub diagnostics: Vec<Diagnostic>,
37 pub dir: Option<std::path::PathBuf>,
39 pub platform: Option<String>,
42}
43
44impl Acceptance {
45 pub fn load(dir: impl AsRef<Path>) -> Result<Self, String> {
49 Self::read(dir.as_ref(), None)
50 }
51
52 pub fn load_for(dir: impl AsRef<Path>, platform: &str) -> Result<Self, String> {
56 let mut a = Self::read(dir.as_ref(), Some(platform))?;
57 a.platform = a.suite.platform.clone();
58 Ok(a)
59 }
60
61 fn read(dir: &Path, platform: Option<&str>) -> Result<Self, String> {
62 let loaded = expectation_language::load_dir_for(dir, platform)
63 .map_err(|e| format!("couldn't read {}: {e}", dir.display()))?;
64 let mut a = Self::from_loaded(loaded)?;
65 a.dir = Some(dir.to_path_buf());
66 Ok(a)
67 }
68
69 pub fn tree(&self, dir: &Path) -> std::path::PathBuf {
72 match self.suite.platform.as_deref() {
73 Some(p) if self.suite.platforms.first().map(String::as_str) != Some(p) => {
74 dir.join("platforms").join(expectation_model::text::slug(p))
75 }
76 _ => dir.to_path_buf(),
77 }
78 }
79
80 pub fn from_loaded(loaded: Loaded) -> Result<Self, String> {
81 let Loaded { suite, mut diagnostics } = loaded;
82 if has_errors(&diagnostics) {
83 return Err(render_all(&diagnostics, &suite.sources));
84 }
85 let analysis = expectation_graph::analyze(&suite);
86 diagnostics.extend(analysis.diagnostics.iter().cloned());
87 let planned = expectation_story::plan_stories(&suite, &analysis);
88 diagnostics.extend(planned.diagnostics);
89 diagnostics.extend(expectation_story::untested(&suite, &analysis, &planned.plans));
90 if has_errors(&diagnostics) {
91 return Err(render_all(&diagnostics, &suite.sources));
92 }
93 Ok(Acceptance { suite, analysis, plans: planned.plans, diagnostics, dir: None, platform: None })
94 }
95
96 pub fn with_coverage(mut self) -> Self {
98 let extra = expectation_story::coverage_plans(&self.suite, &self.analysis, &self.plans);
99 self.plans.extend(extra);
100 self
101 }
102
103 pub fn render_diagnostics(&self) -> String {
104 render_all(&self.diagnostics, &self.suite.sources)
105 }
106
107 pub fn run(&self, driver: &mut dyn Driver, opts: &RunOptions) -> Report {
108 run::run(&self.suite, &self.plans, driver, opts)
109 }
110
111 pub fn run_with(&self, driver: &mut dyn Driver, opts: &RunOptions, services: &mut service::Services) -> Report {
113 run::run_with(&self.suite, &self.plans, driver, opts, services)
114 }
115
116 pub fn docs<'a>(&'a self, report: Option<&'a Report>) -> Docs<'a> {
117 Docs {
118 suite: &self.suite,
119 analysis: &self.analysis,
120 plans: &self.plans,
121 report,
122 suite_dir: self.dir.as_deref(),
123 siblings: &[],
124 outline: None,
125 }
126 }
127
128 pub fn publish(&self, dir: &Path, runner: &str, report: &Report) -> std::io::Result<Report> {
132 let results = dir.join("results");
133 std::fs::create_dir_all(&results)?;
134 let file = expectation_story::results_file(runner, self.platform.as_deref());
135 std::fs::write(results.join(file), report.save(&self.plans))?;
136 let merged = expectation_story::load_results_for(&results, &self.plans, self.suite.platform.as_deref());
137 self.docs(Some(&merged)).write(&self.tree(dir))?;
138 Ok(merged)
139 }
140
141 pub fn assert_passes(&self, driver: &mut dyn Driver, opts: &RunOptions) -> Report {
144 let report = self.run(driver, opts);
145 if let Some(dir) = &opts.docs_dir {
146 self.docs(Some(&report)).write(dir).expect("writing the docs");
147 }
148 if !report.passed() {
149 panic!("acceptance tests failed:\n{}", report.failures(&self.suite, &self.plans));
150 }
151 report
152 }
153}