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 Fn(&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    #[cfg_attr(dylint_lib = "general", allow(non_local_effect_before_error_return))]
116    fn parse(
117        &mut self,
118        context: &LightContext,
119        config: &config::Toml,
120        source_files: &[&Path],
121    ) -> Result<(usize, SourceFileSpanTestMap)> {
122        self.as_parse_mut().parse(context, config, source_files)
123    }
124}
125
126impl<T: AsRun> Run for T {
127    fn dry_run(&self, context: &LightContext, source_file: &Path) -> Result<()> {
128        self.as_run().dry_run(context, source_file)
129    }
130    fn instrument_source_file(
131        &self,
132        context: &LightContext,
133        rewriter: &mut Rewriter,
134        source_file: &SourceFile,
135        n_instrumentable_statements: usize,
136    ) -> Result<()> {
137        self.as_run().instrument_source_file(
138            context,
139            rewriter,
140            source_file,
141            n_instrumentable_statements,
142        )
143    }
144    fn statement_prefix_and_suffix(&self, span: &Span) -> Result<(String, String)> {
145        self.as_run().statement_prefix_and_suffix(span)
146    }
147    fn build_source_file(&self, context: &LightContext, source_file: &Path) -> Result<()> {
148        self.as_run().build_source_file(context, source_file)
149    }
150    fn exec(
151        &self,
152        context: &LightContext,
153        test_name: &str,
154        span: &Span,
155    ) -> Result<Result<(Exec, Option<Box<Postprocess>>)>> {
156        self.as_run().exec(context, test_name, span)
157    }
158}