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, Popen};
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 Fn(&LightContext, Popen) -> 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    fn exec(
94        &self,
95        context: &LightContext,
96        test_name: &str,
97        span: &Span,
98    ) -> Result<Option<(Exec, Option<Box<Postprocess>>)>>;
99}
100
101pub trait AsParse {
102    fn as_parse(&self) -> &dyn Parse;
103    fn as_parse_mut(&mut self) -> &mut dyn Parse;
104}
105
106pub trait AsRun {
107    fn as_run(&self) -> &dyn Run;
108}
109
110impl<T: AsParse> Parse for T {
111    #[cfg_attr(dylint_lib = "general", allow(non_local_effect_before_error_return))]
112    fn parse(
113        &mut self,
114        context: &LightContext,
115        config: &config::Toml,
116        source_files: &[&Path],
117    ) -> Result<(usize, SourceFileSpanTestMap)> {
118        self.as_parse_mut().parse(context, config, source_files)
119    }
120}
121
122impl<T: AsRun> Run for T {
123    fn dry_run(&self, context: &LightContext, source_file: &Path) -> Result<()> {
124        self.as_run().dry_run(context, source_file)
125    }
126    fn instrument_source_file(
127        &self,
128        context: &LightContext,
129        rewriter: &mut Rewriter,
130        source_file: &SourceFile,
131        n_instrumentable_statements: usize,
132    ) -> Result<()> {
133        self.as_run().instrument_source_file(
134            context,
135            rewriter,
136            source_file,
137            n_instrumentable_statements,
138        )
139    }
140    fn statement_prefix_and_suffix(&self, span: &Span) -> Result<(String, String)> {
141        self.as_run().statement_prefix_and_suffix(span)
142    }
143    fn build_source_file(&self, context: &LightContext, source_file: &Path) -> Result<()> {
144        self.as_run().build_source_file(context, source_file)
145    }
146    fn exec(
147        &self,
148        context: &LightContext,
149        test_name: &str,
150        span: &Span,
151    ) -> Result<Option<(Exec, Option<Box<Postprocess>>)>> {
152        self.as_run().exec(context, test_name, span)
153    }
154}