use std::path::{PathBuf, Path};
use std::cell::RefCell;
use std::collections::{HashSet, HashMap};
use std::fs::File;
use std::ffi::OsStr;
use std::io::{Read, BufReader, BufRead};
use cargo::core::Workspace;
use syn::{*, punctuated::{Pair::End, Pair}, spanned::Spanned, punctuated::Punctuated, token::Comma};
use proc_macro2::{Span, TokenTree, TokenStream};
use regex::Regex;
use config::Config;
use walkdir::{DirEntry, WalkDir};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Lines {
All,
Line(usize),
}
#[derive(Clone, Debug)]
pub struct LineAnalysis {
pub ignore: HashSet<Lines>,
pub cover: HashSet<usize>,
}
pub trait SourceAnalysisQuery {
fn should_ignore(&self, path: &Path, l:&usize) -> bool;
}
#[derive(Copy,Clone,Debug)]
enum SubResult {
Ok,
Unreachable
}
impl SourceAnalysisQuery for HashMap<PathBuf, LineAnalysis> {
fn should_ignore(&self, path: &Path, l:&usize) -> bool {
if self.contains_key(path) {
self.get(path).unwrap().should_ignore(*l)
} else {
false
}
}
}
impl LineAnalysis {
fn new() -> LineAnalysis {
LineAnalysis {
ignore: HashSet::new(),
cover: HashSet::new()
}
}
pub fn ignore_all(&mut self) {
self.ignore.clear();
self.cover.clear();
self.ignore.insert(Lines::All);
}
pub fn ignore_span(&mut self, span: Span) {
if !self.ignore.contains(&Lines::All) {
for i in span.start().line..(span.end().line+1) {
self.ignore.insert(Lines::Line(i));
if self.cover.contains(&i) {
self.cover.remove(&i);
}
}
}
}
pub fn cover_span(&mut self, span: Span, contents: Option<&str>) {
let mut useful_lines: HashSet<usize> = HashSet::new();
if let Some(ref c) = contents {
lazy_static! {
static ref SINGLE_LINE: Regex = Regex::new(r"\s*//").unwrap();
static ref MULTI_START: Regex = Regex::new(r"/\*").unwrap();
static ref MULTI_END: Regex = Regex::new(r"\*/").unwrap();
}
let len = span.end().line - span.start().line;
let mut is_comment = false;
for (i, line) in c.lines().enumerate().skip(span.start().line - 1).take(len) {
let is_code = if MULTI_START.is_match(line) {
if !MULTI_END.is_match(line) {
is_comment = true;
}
false
} else if is_comment {
if MULTI_END.is_match(line) {
is_comment = false;
}
false
} else {
true
};
if is_code && !SINGLE_LINE.is_match(line) {
useful_lines.insert(i+1);
}
}
}
for i in span.start().line..(span.end().line +1) {
if !self.ignore.contains(&Lines::Line(i)) && useful_lines.contains(&i) {
self.cover.insert(i);
}
}
}
pub fn should_ignore(&self, line: usize) -> bool {
self.ignore.contains(&Lines::Line(line)) || self.ignore.contains(&Lines::All)
}
fn add_to_ignore(&mut self, lines: &[usize]) {
if !self.ignore.contains(&Lines::All) {
for l in lines {
self.ignore.insert(Lines::Line(*l));
if self.cover.contains(l) {
self.cover.remove(l);
}
}
}
}
}
fn is_source_file(entry: &DirEntry) -> bool {
let p = entry.path();
p.extension() == Some(OsStr::new("rs"))
}
fn is_target_folder(entry: &DirEntry, root: &Path) -> bool {
let target = root.join("target");
entry.path().starts_with(&target)
}
pub fn get_line_analysis(project: &Workspace, config: &Config) -> HashMap<PathBuf, LineAnalysis> {
let mut result: HashMap<PathBuf, LineAnalysis> = HashMap::new();
let mut ignored_files: HashSet<PathBuf> = HashSet::new();
let walker = WalkDir::new(project.root()).into_iter();
for e in walker.filter_entry(|e| !is_target_folder(e, project.root()))
.filter_map(|e| e.ok())
.filter(|e| is_source_file(e)) {
if !ignored_files.contains(e.path()) {
analyse_package(e.path(), project.root(), &config, &mut result, &mut ignored_files);
} else {
let mut analysis = LineAnalysis::new();
analysis.ignore_all();
result.insert(e.path().to_path_buf(), analysis);
ignored_files.remove(e.path());
}
}
for e in &ignored_files {
let mut analysis = LineAnalysis::new();
analysis.ignore_all();
result.insert(e.to_path_buf(), analysis);
}
result
}
fn analyse_lib_rs(file: &Path, result: &mut HashMap<PathBuf, LineAnalysis>) {
if let Ok(f) = File::open(file) {
let mut read_file = BufReader::new(f);
if let Some(Ok(first)) = read_file.lines().nth(0) {
if !(first.starts_with("pub") || first.starts_with("fn")) {
let file = file.to_path_buf();
if result.contains_key(&file) {
let l = result.get_mut(&file).unwrap();
l.add_to_ignore(&[1]);
} else {
let mut l = LineAnalysis::new();
l.add_to_ignore(&[1]);
result.insert(file, l);
}
}
}
}
}
struct Context<'a> {
config: &'a Config,
file_contents: &'a str,
file: &'a Path,
ignore_mods: RefCell<HashSet<PathBuf>>
}
fn analyse_package(path: &Path,
root: &Path,
config:&Config,
result: &mut HashMap<PathBuf, LineAnalysis>,
filtered_files: &mut HashSet<PathBuf>) {
if let Some(file) = path.to_str() {
let skip_cause_test = config.ignore_tests &&
path.starts_with(root.join("tests"));
let skip_cause_example = path.starts_with(root.join("examples"));
if !(skip_cause_test || skip_cause_example) {
let file = File::open(file);
if let Ok(mut file) = file {
let mut content = String::new();
let _ = file.read_to_string(&mut content);
let file = parse_file(&content);
if let Ok(file) = file {
let mut analysis = LineAnalysis::new();
let mut ctx = Context {
config,
file_contents: &content,
file: path,
ignore_mods: RefCell::new(HashSet::new()),
};
find_ignorable_lines(&content, &mut analysis);
process_items(&file.items, &ctx, &mut analysis);
result.insert(path.to_path_buf(), analysis);
let mut ignored_files = ctx.ignore_mods.into_inner();
for f in ignored_files.drain() {
if f.is_file() {
filtered_files.insert(f);
} else {
let walker = WalkDir::new(f).into_iter();
for e in walker.filter_map(|e| e.ok())
.filter(|e| is_source_file(e)) {
filtered_files.insert(e.path().to_path_buf());
}
}
}
if path.ends_with("src/lib.rs") {
analyse_lib_rs(path, result);
}
}
}
}
}
}
fn find_ignorable_lines(content: &str, analysis: &mut LineAnalysis) {
let lines = content.lines()
.enumerate()
.filter(|&(_, x)| !x.chars().any(|x| !"(){}[]?;\t ,".contains(x)))
.map(|(i, _)| i+1)
.collect::<Vec<usize>>();
analysis.add_to_ignore(&lines);
}
fn process_items(items: &[Item], ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
let mut res = SubResult::Ok;
for item in items.iter() {
match *item {
Item::ExternCrate(ref i) => analysis.ignore_span(i.span()),
Item::Use(ref i) => analysis.ignore_span(i.span()),
Item::Mod(ref i) => visit_mod(&i, analysis, ctx),
Item::Fn(ref i) => visit_fn(&i, analysis, ctx),
Item::Struct(ref i) => {
analysis.ignore_span(i.span());
},
Item::Enum(ref i) => {
analysis.ignore_span(i.span());
}
Item::Union(ref i) => {
analysis.ignore_span(i.span());
},
Item::Trait(ref i) => visit_trait(&i, analysis, ctx),
Item::Impl(ref i) => visit_impl(&i, analysis, ctx),
Item::Macro(ref i) => {
if let SubResult::Unreachable = visit_macro_call(&i.mac, ctx, analysis) {
res = SubResult::Unreachable;
}
},
_ =>{}
}
}
res
}
fn process_statements(stmts: &[Stmt], ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
let mut unreachable = false;
for stmt in stmts.iter() {
let res = match *stmt {
Stmt::Item(ref i) => process_items(&[i.clone()], ctx, analysis),
Stmt::Expr(ref i)
| Stmt::Semi(ref i, _) => process_expr(&i, ctx, analysis),
_ => SubResult::Ok,
};
if let SubResult::Unreachable = res {
unreachable = true;
}
}
if unreachable {
SubResult::Unreachable
} else {
SubResult::Ok
}
}
fn visit_mod(module: &ItemMod, analysis: &mut LineAnalysis, ctx: &Context) {
analysis.ignore_span(module.mod_token.span());
let mut check_insides = true;
for attr in &module.attrs {
if let Some(x) = attr.interpret_meta() {
if check_cfg_attr(&x) {
analysis.ignore_span(module.span());
if let Some((ref braces, _)) = module.content {
analysis.ignore_span(braces.span);
}
check_insides = false;
break;
} else if ctx.config.ignore_tests {
if let Meta::List(ref ml) = x {
if ml.ident != "cfg" {
continue;
}
for nested in &ml.nested {
if let NestedMeta::Meta(Meta::Word(ref i)) = *nested {
if i == "test" {
check_insides = false;
analysis.ignore_span(module.mod_token.span());
if let Some((ref braces, _)) = module.content {
analysis.ignore_span(braces.span);
}
}
}
}
}
}
}
}
if check_insides {
if let Some((_, ref items)) = module.content {
process_items(items, ctx, analysis);
}
} else {
let mut p = if let Some(parent) = ctx.file.parent() {
parent.join(module.ident.to_string())
} else {
PathBuf::from(module.ident.to_string())
};
if !p.exists() {
p.set_extension("rs");
}
ctx.ignore_mods.borrow_mut().insert(p);
}
}
fn visit_fn(func: &ItemFn, analysis: &mut LineAnalysis, ctx: &Context) {
let mut test_func = false;
let mut ignored_attr = false;
let mut is_inline = false;
let mut ignore_span = false;
for attr in &func.attrs {
if let Some(x) = attr.interpret_meta() {
let id = x.name();
if id == "test" {
test_func = true;
} else if id == "derive" {
analysis.ignore_span(attr.bracket_token.span);
} else if id == "inline" {
is_inline = true;
} else if id == "ignore" {
ignored_attr = true;
} else if check_cfg_attr(&x) {
ignore_span = true;
break;
}
}
}
if ignore_span {
analysis.ignore_span(func.span());
} else if test_func {
if ctx.config.ignore_tests || (ignored_attr && !ctx.config.run_ignored) {
analysis.ignore_span(func.span());
}
} else {
if is_inline {
analysis.cover_span(func.block.brace_token.span, Some(ctx.file_contents));
}
if let SubResult::Unreachable = process_statements(&func.block.stmts, ctx, analysis) {
analysis.ignore_span(func.span());
return
}
visit_generics(&func.decl.generics, analysis);
let line_number = func.decl.fn_token.span().start().line;
analysis.ignore.remove(&Lines::Line(line_number));
let decl_start = func.decl.fn_token.span().start().line+1;
let stmts_start = func.block.span().start().line;
let lines = (decl_start..(stmts_start+1)).collect::<Vec<_>>();
analysis.add_to_ignore(&lines);
}
}
fn check_attr_list(attrs: &[Attribute], ctx: &Context) -> bool {
let mut check_cover = true;
for attr in attrs {
if let Some(x) = attr.interpret_meta() {
if check_cfg_attr(&x) {
check_cover = false;
} else if ctx.config.ignore_tests && x.name() == "cfg" {
if let Meta::List(ref ml) = x {
let mut skip = false;
for c in &ml.nested {
if let NestedMeta::Meta(Meta::Word(ref i)) = c {
skip |= i == "test";
}
}
if skip {
check_cover = false;
}
}
}
}
if !check_cover {
break;
}
}
check_cover
}
fn check_cfg_attr(attr: &Meta) -> bool {
let mut ignore_span = false;
let id = attr.name();
if id == "cfg_attr" {
if let Meta::List(ml) = attr {
let mut skip_match = false;
let list = vec!["tarpaulin", "skip"];
for (p, x) in ml.nested.iter().zip(list.iter()) {
match p {
NestedMeta::Meta(Meta::Word(ref i)) => {
skip_match = i == x;
},
_ => skip_match = false,
}
if !skip_match {
break;
}
}
ignore_span = skip_match;
}
}
ignore_span
}
fn visit_trait(trait_item: &ItemTrait, analysis: &mut LineAnalysis, ctx: &Context) {
let check_cover = check_attr_list(&trait_item.attrs, ctx);
if check_cover {
for item in &trait_item.items {
if let TraitItem::Method(ref i) = *item {
if check_attr_list(&i.attrs, ctx) {
if let Some(ref block) = i.default {
analysis.cover_span(item.span(), Some(ctx.file_contents));
visit_generics(&i.sig.decl.generics, analysis);
analysis.ignore.remove(&Lines::Line(i.sig.span().start().line));
let decl_start = i.sig.decl.fn_token.span().start().line+1;
let stmts_start = block.span().start().line;
let lines = (decl_start..(stmts_start+1)).collect::<Vec<_>>();
analysis.add_to_ignore(&lines);
}
} else {
analysis.ignore_span(i.span());
}
for a in &i.attrs {
analysis.ignore_span(a.span());
}
}
}
visit_generics(&trait_item.generics, analysis);
} else {
analysis.ignore_span(trait_item.span());
}
}
fn visit_impl(impl_blk: &ItemImpl, analysis: &mut LineAnalysis, ctx: &Context) {
let check_cover = check_attr_list(&impl_blk.attrs, ctx);
if check_cover {
for item in &impl_blk.items {
if let ImplItem::Method(ref i) = *item {
if check_attr_list(&i.attrs, ctx) {
analysis.cover_span(i.span(), Some(ctx.file_contents));
if let SubResult::Unreachable = process_statements(&i.block.stmts, ctx, analysis) {
analysis.ignore_span(i.span());
return
}
visit_generics(&i.sig.decl.generics, analysis);
analysis.ignore.remove(&Lines::Line(i.span().start().line));
let decl_start = i.sig.decl.fn_token.span().start().line+1;
let stmts_start = i.block.span().start().line;
let lines = (decl_start..(stmts_start+1)).collect::<Vec<_>>();
analysis.add_to_ignore(&lines);
} else {
analysis.ignore_span(item.span());
}
for a in &i.attrs {
analysis.ignore_span(a.span());
}
}
}
visit_generics(&impl_blk.generics, analysis);
} else {
analysis.ignore_span(impl_blk.span());
}
}
fn visit_generics(generics: &Generics, analysis: &mut LineAnalysis) {
if let Some(ref wh) = generics.where_clause {
analysis.ignore_span(wh.span());
}
}
fn process_expr(expr: &Expr, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
let res = match *expr {
Expr::Macro(ref m) => visit_macro_call(&m.mac, ctx, analysis),
Expr::Struct(ref s) => visit_struct_expr(&s, analysis),
Expr::Unsafe(ref u) => visit_unsafe_block(&u, ctx, analysis),
Expr::Call(ref c) => visit_callable(&c, analysis),
Expr::MethodCall(ref m) => visit_methodcall(&m, analysis),
Expr::Match(ref m) => visit_match(&m, ctx, analysis),
Expr::Block(ref b) => visit_block(&b.block, ctx, analysis),
Expr::If(ref i) => visit_if(&i, ctx, analysis),
Expr::While(ref w) => visit_while(&w, ctx, analysis),
Expr::ForLoop(ref f) => visit_for(&f, ctx, analysis),
Expr::Loop(ref l) => visit_loop(&l, ctx, analysis),
_ => SubResult::Ok,
};
if let SubResult::Unreachable = res {
analysis.ignore_span(expr.span());
}
res
}
fn visit_block(block: &Block, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
if let SubResult::Unreachable = process_statements(&block.stmts, ctx, analysis) {
analysis.ignore_span(block.span());
SubResult::Unreachable
} else {
SubResult::Ok
}
}
fn visit_match(mat: &ExprMatch, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
let mut reachable_arm = false;
for arm in &mat.arms {
if let SubResult::Ok = process_expr(&arm.body, ctx, analysis) {
reachable_arm = true
}
}
if !reachable_arm {
analysis.ignore_span(mat.span());
SubResult::Unreachable
} else {
SubResult::Ok
}
}
fn visit_if(if_block: &ExprIf, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
let mut reachable_arm = false;
if let SubResult::Ok = visit_block(&if_block.then_branch, ctx, analysis) {
reachable_arm = true;
}
if let Some((_ ,ref else_block)) = if_block.else_branch {
if let SubResult::Ok = process_expr(&else_block, ctx, analysis) {
reachable_arm = true;
}
} else {
reachable_arm = true;
}
if !reachable_arm {
analysis.ignore_span(if_block.span());
SubResult::Unreachable
} else {
SubResult::Ok
}
}
fn visit_while(whl: &ExprWhile, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
if let SubResult::Unreachable = visit_block(&whl.body, ctx, analysis) {
analysis.ignore_span(whl.span());
SubResult::Unreachable
} else {
SubResult::Ok
}
}
fn visit_for(for_loop: &ExprForLoop, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
if let SubResult::Unreachable = visit_block(&for_loop.body, ctx, analysis) {
analysis.ignore_span(for_loop.span());
SubResult::Unreachable
} else {
SubResult::Ok
}
}
fn visit_loop(loopex: &ExprLoop, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
if let SubResult::Unreachable = visit_block(&loopex.body, ctx, analysis) {
analysis.ignore_span(loopex.span());
SubResult::Unreachable
} else {
SubResult::Ok
}
}
fn get_coverable_args(args: &Punctuated<Expr, Comma>) -> HashSet<usize> {
let mut lines:HashSet<usize> = HashSet::new();
for a in args.iter() {
let s = a.span();
match *a {
Expr::Lit(_) => {},
_ => {
for i in s.start().line..(s.end().line+1) {
lines.insert(i);
}
}
}
}
lines
}
fn visit_callable(call: &ExprCall, analysis: &mut LineAnalysis ) -> SubResult {
let start = call.span().start().line + 1;
let end = call.span().end().line + 1;
let lines = get_coverable_args(&call.args);
let lines = (start..end).filter(|x| !lines.contains(&x))
.collect::<Vec<_>>();
analysis.add_to_ignore(&lines);
SubResult::Ok
}
fn visit_methodcall(meth: &ExprMethodCall, analysis: &mut LineAnalysis) -> SubResult {
let start = meth.span().start().line + 1;
let end = meth.span().end().line + 1;
let lines = get_coverable_args(&meth.args);
let lines = (start..end).filter(|x| !lines.contains(&x))
.collect::<Vec<_>>();
analysis.add_to_ignore(&lines);
SubResult::Ok
}
fn visit_unsafe_block(unsafe_expr: &ExprUnsafe, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
let u_line = unsafe_expr.unsafe_token.span().start().line;
let blk = &unsafe_expr.block;
if u_line != blk.brace_token.span.start().line || blk.stmts.is_empty() {
analysis.ignore_span(unsafe_expr.unsafe_token.span());
} else if let Some(ref first_stmt) = blk.stmts.get(0) {
let s = match **first_stmt {
Stmt::Local(ref l) => l.span(),
Stmt::Item(ref i) => i.span(),
Stmt::Expr(ref e) => e.span(),
Stmt::Semi(ref e, _) => e.span(),
};
if u_line != s.start().line {
analysis.ignore_span(unsafe_expr.unsafe_token.span());
}
if let SubResult::Unreachable = process_statements(&blk.stmts, ctx, analysis) {
analysis.ignore_span(unsafe_expr.span());
return SubResult::Unreachable;
}
} else {
analysis.ignore_span(unsafe_expr.unsafe_token.span());
analysis.ignore_span(blk.brace_token.span);
}
SubResult::Ok
}
fn visit_struct_expr(structure: &ExprStruct, analysis: &mut LineAnalysis) -> SubResult {
let mut cover: HashSet<usize> = HashSet::new();
let start = structure.span().start().line;
let end = structure.span().end().line;
for field in structure.fields.pairs() {
let first = match field {
Pair::Punctuated(t, _) => {t},
Pair::End(t) => {t},
};
let span = match first.member {
Member::Named(ref i) => i.span(),
Member::Unnamed(ref i) => i.span,
};
match first.expr {
Expr::Lit(_) | Expr::Path(_) => {},
_=>{
cover.insert(span.start().line);
},
}
}
let x = (start..(end+1)).filter(|x| !cover.contains(&x))
.collect::<Vec<usize>>();
analysis.add_to_ignore(&x);
SubResult::Ok
}
fn visit_macro_call(mac: &Macro, ctx: &Context, analysis: &mut LineAnalysis) -> SubResult {
let mut skip = false;
let start = mac.span().start().line + 1;
let end = mac.span().end().line + 1;
if let Some(End(ref name)) = mac.path.segments.last() {
let unreachable = name.ident == "unreachable";
let standard_ignores = name.ident == "unimplemented" || name.ident == "include";
let ignore_panic = ctx.config.ignore_panics && name.ident == "panic";
if standard_ignores || ignore_panic || unreachable {
analysis.ignore_span(mac.span());
skip = true;
}
if unreachable {
return SubResult::Unreachable
}
}
if !skip {
let lines = process_mac_args(&mac.tts);
let lines = (start..end).filter(|x| !lines.contains(&x))
.collect::<Vec<_>>();
analysis.add_to_ignore(&lines);
}
SubResult::Ok
}
fn process_mac_args(tokens: &TokenStream) -> HashSet<usize> {
let mut cover: HashSet<usize> = HashSet::new();
for token in tokens.clone() {
let t = token.span();
match token {
TokenTree::Literal(_) | TokenTree::Punct{..} => {},
_ => {
for i in t.start().line..(t.end().line+1) {
cover.insert(i);
}
},
}
}
cover
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_file;
#[test]
fn line_analysis_works() {
let mut la = LineAnalysis::new();
assert!(!la.should_ignore(0));
assert!(!la.should_ignore(10));
la.add_to_ignore(&[3,4, 10]);
assert!(la.should_ignore(3));
assert!(la.should_ignore(4));
assert!(la.should_ignore(10));
assert!(!la.should_ignore(1));
}
#[test]
fn filter_str_literals() {
let mut lines = LineAnalysis::new();
let config = Config::default();
let ctx = Context {
config: &config,
file_contents: "fn test() {\nwriteln!(#\"test\n\ttest\n\ttest\"#);\n}\n",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.len() > 1);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
let ctx = Context {
config: &config,
file_contents: "fn test() {\nwrite(\"test\ntest\ntest\");\n}\nfn write(s:&str){}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
let mut lines = LineAnalysis::new();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.len() > 1);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "\n\nfn test() {\nwriteln!(\n#\"test\"#\n);\n}\n",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(5)));
}
#[test]
fn filter_struct_members() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[derive(Debug)]\npub struct Struct {\npub i: i32,\nj:String,\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.len()> 3);
assert!(lines.ignore.contains(&Lines::Line(1)));
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
let ctx = Context {
config: &config,
file_contents: "#[derive(Debug)]\npub struct Struct (\n i32\n);",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.is_empty());
assert!(lines.ignore.contains(&Lines::Line(3)));
}
#[test]
fn filter_enum_members() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[derive(Debug)]\npub enum E {\nI1,\nI2(u32),\nI3{\nx:u32,\n},\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.len()> 3);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(5)));
assert!(lines.ignore.contains(&Lines::Line(6)));
assert!(lines.ignore.contains(&Lines::Line(7)));
}
#[test]
fn filter_struct_consts() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "struct T{x:String, y:i32}
fn test()-> T {
T{
x:String::from(\"hello\"), //function call should be covered
y:4,
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(5)));
}
#[test]
fn filter_mods() {
let config = Config::default();
let ctx = Context {
config: &config,
file_contents: "mod foo {\nfn double(x:i32)->i32 {\n x*2\n}\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
let mut lines = LineAnalysis::new();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(3)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "mod foo;",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(1)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "mod foo{}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(1)));
}
#[test]
fn filter_macros() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "\n\nfn unused() {\nunimplemented!();\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.len() >= 1);
assert!(lines.ignore.contains(&Lines::Line(4)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "\n\nfn unused() {\nunreachable!();\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.len() >= 1);
assert!(lines.ignore.contains(&Lines::Line(4)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn unreachable_match(x: u32) -> u32 {
match x {
1 => 5,
2 => 7,
_ => unreachable!(),
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(5)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn unused() {\nprintln!(\"text\");\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(2)));
}
#[test]
fn filter_tests() {
let config = Config::default();
let mut igconfig = Config::default();
igconfig.ignore_tests = true;
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[cfg(test)]\nmod tests {\n fn boo(){\nassert!(true);\n}\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(4)));
let ctx = Context {
config: &igconfig,
file_contents: "#[cfg(test)]\nmod tests {\n fn boo(){\nassert!(true);\n}\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let mut lines = LineAnalysis::new();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(4)));
let ctx = Context {
config: &config,
file_contents: "#[test]\nfn mytest() { \n assert!(true);\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
let mut lines = LineAnalysis::new();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(2)));
assert!(!lines.ignore.contains(&Lines::Line(3)));
let ctx = Context {
config: &igconfig,
file_contents: "#[test]\nfn mytest() { \n assert!(true);\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let mut lines = LineAnalysis::new();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(2)));
assert!(lines.ignore.contains(&Lines::Line(3)));
}
#[test]
fn filter_test_utilities() {
let mut config = Config::default();
config.ignore_tests = true;
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "trait Thing {
#[cfg(test)]
fn boo(){
assert!(true);
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(2)));
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "trait Thing {
#[cfg(test)]
fn boo(){
assert!(true);
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(3)));
assert!(!lines.ignore.contains(&Lines::Line(4)));
}
#[test]
fn filter_where() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn boop<T>() -> T where T:Default {
T::default()
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(1)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn boop<T>() -> T
where T:Default {
T::default()
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(2)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "trait foof {
fn boop<T>() -> T
where T:Default {
T::default()
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(3)));
}
#[test]
fn filter_derives() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[derive(Debug)]\nstruct T;",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(1)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "\n#[derive(Copy, Eq)]\nunion x { x:i32, y:f32}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(2)));
}
#[test]
fn filter_unsafe() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn unsafe_fn() {\n let x=1;\nunsafe {\nprintln!(\"{}\", x);\n}\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(!lines.ignore.contains(&Lines::Line(4)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn unsafe_fn() {\n let x=1;\nunsafe {println!(\"{}\", x);}\n}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(3)));
}
#[test]
fn cover_generic_impl_methods() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "struct GenericStruct<T>(T);
impl<T> GenericStruct<T> {
fn hw(&self) {
println!(\"hello world\");
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.cover.contains(&3));
assert!(lines.cover.contains(&4));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "struct GenericStruct<T>{v:Vec<T>}
impl<T> Default for GenericStruct<T> {
fn default() -> Self {
T {
v: vec![],
}
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.cover.contains(&5));
}
#[test]
fn cover_default_trait_methods() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "trait Thing {
fn hw(&self) {
println!(\"hello world\");
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.cover.contains(&2));
assert!(lines.cover.contains(&3));
}
#[test]
fn filter_method_args() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "struct Thing;
impl Thing{
fn hw(&self, name: &str) {
println!(\"hello {}\", name);
} //5
}
fn get_name() -> String {
return \"Daniel\".to_string()
} //10
fn main() {
let s=Thing{};
s.hw(
\"Paul\" //15
);
s.hw(
&get_name()
); //20
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(15)));
assert!(!lines.ignore.contains(&Lines::Line(19)));
}
#[test]
fn filter_use_statements() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "use std::collections::HashMap;
use std::{ffi::CString, os::raw::c_char};",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(1)));
assert!(lines.ignore.contains(&Lines::Line(2)));
}
#[test]
fn include_inline_fns() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[inline]
fn inline_func() {
// I shouldn't be covered
println!(\"I should\");
/*
None of us should
*/
println!(\"But I will\");
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.cover.contains(&3));
assert!(lines.cover.contains(&4));
assert!(!lines.cover.contains(&5));
assert!(!lines.cover.contains(&6));
assert!(!lines.cover.contains(&7));
assert!(lines.cover.contains(&8));
}
#[test]
fn tarpaulin_skip_attr() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[cfg_attr(tarpaulin, skip)]
fn skipped() {
println!(\"Hello world\");
}
#[cfg_attr(tarpaulin, not_a_thing)]
fn covered() {
println!(\"hell world\");
}
",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(2)));
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(!lines.ignore.contains(&Lines::Line(7)));
assert!(!lines.ignore.contains(&Lines::Line(8)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[cfg_attr(tarpaulin, skip)]
mod ignore_all {
fn skipped() {
println!(\"Hello world\");
}
#[cfg_attr(tarpaulin, not_a_thing)]
fn covered() {
println!(\"hell world\");
}
}
",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(8)));
assert!(lines.ignore.contains(&Lines::Line(9)));
}
#[test]
fn tarpaulin_skip_trait_attrs() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "#[cfg_attr(tarpaulin, skip)]
trait Foo {
fn bar() {
println!(\"Hello world\");
}
fn not_covered() {
println!(\"hell world\");
}
}
",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(8)));
assert!(lines.ignore.contains(&Lines::Line(9)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "trait Foo {
fn bar() {
println!(\"Hello world\");
}
#[cfg_attr(tarpaulin, skip)]
fn not_covered() {
println!(\"hell world\");
}
}
",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(2)));
assert!(!lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(7)));
assert!(lines.ignore.contains(&Lines::Line(8)));
}
#[test]
fn tarpaulin_skip_impl_attrs() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "struct Foo;
#[cfg_attr(tarpaulin, skip)]
impl Foo {
fn bar() {
println!(\"Hello world\");
}
fn not_covered() {
println!(\"hell world\");
}
}
",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(5)));
assert!(lines.ignore.contains(&Lines::Line(9)));
assert!(lines.ignore.contains(&Lines::Line(10)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "struct Foo;
impl Foo {
fn bar() {
println!(\"Hello world\");
}
#[cfg_attr(tarpaulin, skip)]
fn not_covered() {
println!(\"hell world\");
}
}
",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(3)));
assert!(!lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(9)));
assert!(lines.ignore.contains(&Lines::Line(10)));
}
#[test]
fn filter_block_contents() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn unreachable_match(x: u32) -> u32 {
match x {
1 => 5,
2 => 7,
_ => {
unreachable!();
},
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(6)));
}
#[test]
fn optional_panic_ignore() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn unreachable_match(x: u32) -> u32 {
match x {
1 => 5,
2 => 7,
_ => panic!(),
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(!lines.ignore.contains(&Lines::Line(5)));
let mut config = Config::default();
config.ignore_panics = true;
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn unreachable_match(x: u32) -> u32 {
match x {
1 => 5,
2 => 7,
_ => panic!(),
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(5)));
}
#[test]
fn filter_nested_blocks() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn block() {
{
loop {
for i in 1..2 {
if false {
while let Some(x) = Some(6) {
while false {
if let Ok(y) = Ok(4) {
unreachable!();
}
}
}
}
}
}
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(9)));
}
#[test]
fn filter_multi_line_decls() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn print_it(x:u32,
y:u32,
z:u32) {
println!(\"{}:{}:{}\",x,y,z);
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(2)));
assert!(lines.ignore.contains(&Lines::Line(3)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "struct Boo;
impl Boo {
fn print_it(x:u32,
y:u32,
z:u32) {
println!(\"{}:{}:{}\",x,y,z);
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(5)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "trait Boo {
fn print_it(x:u32,
y:u32,
z:u32) {
println!(\"{}:{}:{}\",x,y,z);
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
}
#[test]
fn unreachable_propagate() {
let config = Config::default();
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "enum Void {}
fn empty_match(x: Void) -> u32 {
match x {
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(2)));
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(5)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn foo() {
if random() {
loop {
match random() {
true => match void() {},
false => unreachable!()
}
}
} else {
call();
}
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(5)));
assert!(lines.ignore.contains(&Lines::Line(6)));
assert!(lines.ignore.contains(&Lines::Line(7)));
assert!(lines.ignore.contains(&Lines::Line(8)));
let mut lines = LineAnalysis::new();
let ctx = Context {
config: &config,
file_contents: "fn test_unreachable() {
let x: u32 = foo();
if x > 5 {
bar();
}
unreachable!();
}",
file: Path::new(""),
ignore_mods: RefCell::new(HashSet::new()),
};
let parser = parse_file(ctx.file_contents).unwrap();
process_items(&parser.items, &ctx, &mut lines);
assert!(lines.ignore.contains(&Lines::Line(1)));
assert!(lines.ignore.contains(&Lines::Line(2)));
assert!(lines.ignore.contains(&Lines::Line(3)));
assert!(lines.ignore.contains(&Lines::Line(4)));
assert!(lines.ignore.contains(&Lines::Line(5)));
assert!(lines.ignore.contains(&Lines::Line(6)));
assert!(lines.ignore.contains(&Lines::Line(7)));
}
}