use rune::{
termcolor::{ColorChoice, StandardStream},
Diagnostics, Vm,
};
use std::{io::Read, path::Path, sync::Arc};
use crate::{
prelude::*,
report_generators::{Generator, StoryReportGenerator},
};
pub(crate) async fn execute_story(
story: &story::Story,
stories: &story::Stories,
base_path: &Path,
runtime: Arc<tokio::runtime::Runtime>,
report_generator: &report_generators::GeneratorDispatcher,
) -> anyhow::Result<bool> {
let mut rune_context = rune_modules::default_context()?;
modules::install_standard_modules(&mut rune_context)?;
let filename = story
.filename
.clone()
.or_else(|| stories.filename.clone())
.ok_or_else(|| {
anyhow::Error::msg(format!(
"No source filename was provided for test '{}'.",
story.name
))
})?;
let filename_path = Path::new(&filename);
let filename_resolved = if filename_path.is_absolute() {
filename.clone().into()
} else {
base_path.join(&filename)
};
let mut file = std::fs::File::open(&filename_resolved)?;
let mut data = String::new();
file.read_to_string(&mut data)?;
let mut sources = rune::Sources::new();
sources.insert(rune::Source::new(&filename, data)?)?;
let mut diagnostics = Diagnostics::new();
let result = rune::prepare(&mut sources)
.with_context(&rune_context)
.with_diagnostics(&mut diagnostics)
.build();
if !diagnostics.is_empty() {
let mut writer = StandardStream::stderr(ColorChoice::Always);
diagnostics.emit(&mut writer, &sources)?;
}
let unit = result?;
let mut vm = Vm::new(Arc::new(rune_context.runtime()?), Arc::new(unit));
let reporter = Reporter::new();
let stories_context = Context::new(runtime, reporter);
let start = std::time::Instant::now();
let r = vm
.execute([story.function.as_str()], (stories_context.clone(),))?
.complete()
.into_result();
let duration = std::time::Instant::now() - start;
if let Err(error) = r {
report_generator.report_failure(
story,
duration,
format!(
"function {} in file {} is invalid with error '{}'.",
story.function, filename, error
),
);
for error in error.chain().into_iter().skip(1) {
log::error!("Caused by: {}", error);
}
return Ok(false);
}
stories_context
.finalise_activities(std::time::Duration::from_secs(2))
.await;
let mut result = false;
let mut story_generator = None;
if story.expect_failure {
if stories_context.is_successful() {
story_generator = Some(report_generator.report_failure(
story,
duration,
"story was successful, however, failure was expected.".into(),
));
} else {
let messages = stories_context.reporter_ref().clone_messages();
if story.messages.len() == messages.len() {
let mut missing_match = false;
for expected_message in &story.messages {
let mut found = false;
let expected_regex = regex::Regex::new(&expected_message.content)?;
for actual_message in messages.iter() {
if expected_message.line == actual_message.line
&& expected_message.filename == actual_message.filename
&& expected_regex.is_match(&actual_message.content)
{
found = true;
break;
}
}
if !found {
missing_match = true;
story_generator = Some(report_generator.report_failure(
story,
duration,
format!(
"{}: missing message '{}:{}: {}'!",
story.name,
expected_message.filename,
expected_message.line,
expected_message.content
),
));
}
}
if !missing_match {
result = true;
}
} else {
story_generator = Some(report_generator.report_failure(
story,
duration,
format!(
"{}: failed with {} messages instead of {}.",
story.name,
messages.len(),
story.messages.len()
),
));
}
}
} else {
if stories_context.is_successful() {
result = true;
} else {
story_generator = Some(report_generator.report_failure(
story,
duration,
format!(
"failed, with '{}' messages:",
stories_context.reporter_ref().messages_count()
),
));
result = false;
}
}
if result {
story_generator = Some(report_generator.report_success(story, duration));
}
let story_generator = story_generator
.ok_or_else(|| anyhow::Error::msg("somehow a story generator was not constructed."))?;
stories_context
.reporter_ref()
.for_each_message(|message| story_generator.report_message(&message));
Ok(result)
}