Skip to main content

necessist_core/framework/
mod.rs

1use crate::{LightContext, SourceFile, Span, config, rewriter::Rewriter};
2use anyhow::Result;
3use indexmap::IndexSet;
4use std::{
5    collections::{BTreeMap, BTreeSet},
6    path::Path,
7};
8use subprocess::{Exec, Job};
9
10mod auto;
11pub use auto::Auto;
12
13mod empty;
14pub use empty::Empty;
15
16mod union;
17pub use union::Union;
18
19#[allow(dead_code)]
20type AutoUnion<T, U> = Auto<Union<T, U>>;
21
22pub trait Applicable {
23    fn applicable(&self, context: &LightContext) -> Result<bool>;
24}
25
26pub trait ToImplementation {
27    fn to_implementation(&self, context: &LightContext) -> Result<Option<Box<dyn Interface>>>;
28}
29
30pub trait Interface: Parse + Run {}
31
32pub type TestSet = BTreeSet<String>;
33
34pub type SourceFileSpanTestMap = BTreeMap<SourceFile, SpanTestMaps>;
35
36#[derive(Default)]
37pub struct SpanTestMaps {
38    pub statement: SpanTestMap,
39    pub method_call: SpanTestMap,
40}
41
42impl SpanTestMaps {
43    pub fn iter(&self) -> impl Iterator<Item = (&Span, SpanKind, &IndexSet<String>)> {
44        self.statement
45            .iter()
46            .map(|(span, test_names)| (span, SpanKind::Statement, test_names))
47            .chain(
48                self.method_call
49                    .iter()
50                    .map(|(span, test_names)| (span, SpanKind::MethodCall, test_names)),
51            )
52    }
53}
54
55/// Maps a [`Span`] to the names of the tests that exercise it
56///
57/// The test names are needed because they are passed to [`Run::exec`].
58pub type SpanTestMap = BTreeMap<Span, IndexSet<String>>;
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub enum SpanKind {
62    Statement,
63    MethodCall,
64}
65
66pub trait Parse {
67    fn parse(
68        &mut self,
69        context: &LightContext,
70        config: &config::Toml,
71        source_files: &[&Path],
72    ) -> Result<(usize, SourceFileSpanTestMap)>;
73}
74
75pub type Postprocess = dyn FnOnce(&LightContext, Job) -> Result<bool>;
76
77pub trait Run {
78    fn dry_run(&self, context: &LightContext, source_file: &Path) -> Result<()>;
79    fn instrument_source_file(
80        &self,
81        context: &LightContext,
82        rewriter: &mut Rewriter,
83        source_file: &SourceFile,
84        n_instrumentable_statements: usize,
85    ) -> Result<()>;
86    fn statement_prefix_and_suffix(&self, span: &Span) -> Result<(String, String)>;
87    fn build_source_file(&self, context: &LightContext, source_file: &Path) -> Result<()>;
88    /// Execute test `test_name` with `span` removed. Returns `Ok(None)` if the test could not be
89    /// built.
90    ///
91    /// In most cases, just `span.source_file` is used. But in the implementation of `RunLow::exec`,
92    /// `span` is used for error reporting.
93    ///
94    /// `Result<Result<..>>` is a questionable return type. The inner `Result` represents the result
95    /// of trying to build the project under test. Having it is useful for when the "Instrumentation
96    /// failed to build after it was verified to" assertion fails.
97    fn exec(
98        &self,
99        context: &LightContext,
100        test_name: &str,
101        span: &Span,
102    ) -> Result<Result<(Exec, Option<Box<Postprocess>>)>>;
103}
104
105pub trait AsParse {
106    fn as_parse(&self) -> &dyn Parse;
107    fn as_parse_mut(&mut self) -> &mut dyn Parse;
108}
109
110pub trait AsRun {
111    fn as_run(&self) -> &dyn Run;
112}
113
114impl<T: AsParse> Parse for T {
115    fn parse(
116        &mut self,
117        context: &LightContext,
118        config: &config::Toml,
119        source_files: &[&Path],
120    ) -> Result<(usize, SourceFileSpanTestMap)> {
121        self.as_parse_mut().parse(context, config, source_files)
122    }
123}
124
125impl<T: AsRun> Run for T {
126    fn dry_run(&self, context: &LightContext, source_file: &Path) -> Result<()> {
127        self.as_run().dry_run(context, source_file)
128    }
129    fn instrument_source_file(
130        &self,
131        context: &LightContext,
132        rewriter: &mut Rewriter,
133        source_file: &SourceFile,
134        n_instrumentable_statements: usize,
135    ) -> Result<()> {
136        self.as_run().instrument_source_file(
137            context,
138            rewriter,
139            source_file,
140            n_instrumentable_statements,
141        )
142    }
143    fn statement_prefix_and_suffix(&self, span: &Span) -> Result<(String, String)> {
144        self.as_run().statement_prefix_and_suffix(span)
145    }
146    fn build_source_file(&self, context: &LightContext, source_file: &Path) -> Result<()> {
147        self.as_run().build_source_file(context, source_file)
148    }
149    fn exec(
150        &self,
151        context: &LightContext,
152        test_name: &str,
153        span: &Span,
154    ) -> Result<Result<(Exec, Option<Box<Postprocess>>)>> {
155        self.as_run().exec(context, test_name, span)
156    }
157}