Skip to main content

grcov/
producer.rs

1use rustc_hash::FxHashMap;
2use std::cell::RefCell;
3use std::env;
4use std::fs::{self, File};
5use std::io::{self, BufReader, Read};
6use std::path::{Path, PathBuf};
7use walkdir::WalkDir;
8use zip::ZipArchive;
9
10use crate::defs::*;
11
12#[derive(Debug)]
13pub enum ArchiveType {
14    Zip(RefCell<ZipArchive<BufReader<File>>>),
15    Dir(PathBuf),
16    Plain(Vec<PathBuf>),
17}
18
19#[derive(Debug)]
20pub struct Archive {
21    pub name: String,
22    pub item: RefCell<ArchiveType>,
23}
24
25#[derive(Debug, PartialEq, Eq, Hash)]
26pub struct GCNOStem {
27    pub stem: String,
28    pub llvm: bool,
29}
30
31#[cfg(not(windows))]
32fn clean_path(path: &Path) -> String {
33    path.to_str().unwrap().to_string()
34}
35
36#[cfg(windows)]
37fn clean_path(path: &Path) -> String {
38    path.to_str().unwrap().to_string().replace("\\", "/")
39}
40
41impl Archive {
42    fn insert_vec<'a>(
43        &'a self,
44        filename: String,
45        map: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
46    ) {
47        let mut map = map.borrow_mut();
48        map.entry(filename)
49            .or_insert_with(|| Vec::with_capacity(1))
50            .push(self);
51    }
52
53    fn handle_file<'a>(
54        &'a self,
55        file: Option<&mut impl Read>,
56        path: &Path,
57        gcno_stem_archives: &RefCell<FxHashMap<GCNOStem, &'a Archive>>,
58        gcda_stem_archives: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
59        profdatas: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
60        profraws: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
61        infos: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
62        xmls: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
63        gocovs: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
64        linked_files_maps: &RefCell<FxHashMap<String, &'a Archive>>,
65        is_llvm: bool,
66    ) {
67        if let Some(ext) = path.extension() {
68            match ext.to_str().unwrap() {
69                "gcno" => {
70                    let llvm = is_llvm || Archive::check_file(file, &Archive::is_gcno_llvm);
71                    let filename = clean_path(&path.with_extension(""));
72                    gcno_stem_archives.borrow_mut().insert(
73                        GCNOStem {
74                            stem: filename,
75                            llvm,
76                        },
77                        self,
78                    );
79                }
80                "gcda" => {
81                    let filename = clean_path(&path.with_extension(""));
82                    self.insert_vec(filename, gcda_stem_archives);
83                }
84                "profdata" => {
85                    let filename = clean_path(path);
86                    self.insert_vec(filename, profdatas);
87                }
88                "profraw" => {
89                    let filename = clean_path(path);
90                    self.insert_vec(filename, profraws);
91                }
92                "info" | "dat" => {
93                    if Archive::check_file(file, &Archive::is_info) {
94                        let filename = clean_path(path);
95                        self.insert_vec(filename, infos);
96                    }
97                }
98                "xml" => {
99                    if Archive::check_file(file, &Archive::is_jacoco) {
100                        let filename = clean_path(path);
101                        self.insert_vec(filename, xmls);
102                    }
103                }
104                "json" => {
105                    let filename = path.file_name().unwrap();
106                    if filename == "linked-files-map.json" {
107                        let filename = clean_path(path);
108                        linked_files_maps.borrow_mut().insert(filename, self);
109                    }
110                }
111                "out" => {
112                    if Archive::check_file(file, &Archive::is_go_cov) {
113                        let filename = clean_path(path);
114                        self.insert_vec(filename, gocovs);
115                    }
116                }
117                _ => {}
118            }
119        }
120    }
121
122    fn is_gcno_llvm(reader: &mut dyn Read) -> bool {
123        let mut bytes: [u8; 8] = [0; 8];
124        reader.read_exact(&mut bytes).is_ok()
125            && &bytes[..5] == b"oncg*"
126            && (&bytes[5..] == b"204" || &bytes[5..] == b"804")
127    }
128
129    fn is_jacoco(reader: &mut dyn Read) -> bool {
130        let mut bytes: [u8; 256] = [0; 256];
131        if reader.read_exact(&mut bytes).is_ok() {
132            return match String::from_utf8(bytes.to_vec()) {
133                Ok(s) => s.contains("-//JACOCO//DTD"),
134                Err(_) => false,
135            };
136        }
137        false
138    }
139
140    fn is_go_cov(reader: &mut dyn Read) -> bool {
141        let mut bytes: [u8; 8] = [0; 8];
142        reader.read_exact(&mut bytes).is_ok() && &bytes[..5] == b"mode:"
143    }
144
145    fn is_info(reader: &mut dyn Read) -> bool {
146        let mut bytes: [u8; 3] = [0; 3];
147        reader.read_exact(&mut bytes).is_ok()
148            && (bytes == [b'T', b'N', b':'] || bytes == [b'S', b'F', b':'])
149    }
150
151    fn check_file(file: Option<&mut impl Read>, checker: &dyn Fn(&mut dyn Read) -> bool) -> bool {
152        file.is_some_and(|f| checker(f))
153    }
154
155    pub fn get_name(&self) -> &String {
156        &self.name
157    }
158
159    pub fn explore<'a>(
160        &'a mut self,
161        gcno_stem_archives: &RefCell<FxHashMap<GCNOStem, &'a Archive>>,
162        gcda_stem_archives: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
163        profdatas: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
164        profraws: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
165        infos: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
166        xmls: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
167        gocovs: &RefCell<FxHashMap<String, Vec<&'a Archive>>>,
168        linked_files_maps: &RefCell<FxHashMap<String, &'a Archive>>,
169        is_llvm: bool,
170    ) {
171        match *self.item.borrow() {
172            ArchiveType::Zip(ref zip) => {
173                let mut zip = zip.borrow_mut();
174                for i in 0..zip.len() {
175                    let mut file = zip.by_index(i).unwrap();
176                    let path = PathBuf::from(file.name());
177                    self.handle_file(
178                        Some(&mut file),
179                        &path,
180                        gcno_stem_archives,
181                        gcda_stem_archives,
182                        profdatas,
183                        profraws,
184                        infos,
185                        xmls,
186                        gocovs,
187                        linked_files_maps,
188                        is_llvm,
189                    );
190                }
191            }
192            ArchiveType::Dir(ref dir) => {
193                for entry in WalkDir::new(dir) {
194                    let entry = entry.unwrap_or_else(|err| {
195                        panic!(
196                            "Failed to open '{}'.",
197                            err.path().unwrap().to_string_lossy()
198                        )
199                    });
200                    let full_path = entry.path();
201                    if full_path.is_file() {
202                        let mut file = File::open(full_path).ok();
203                        let path = full_path.strip_prefix(dir).unwrap();
204                        self.handle_file(
205                            file.as_mut(),
206                            path,
207                            gcno_stem_archives,
208                            gcda_stem_archives,
209                            profdatas,
210                            profraws,
211                            infos,
212                            xmls,
213                            gocovs,
214                            linked_files_maps,
215                            is_llvm,
216                        );
217                    }
218                }
219            }
220            ArchiveType::Plain(ref plain) => {
221                // All the paths are absolutes
222                for full_path in plain {
223                    let mut file = File::open(full_path).ok();
224                    self.handle_file(
225                        file.as_mut(),
226                        full_path,
227                        gcno_stem_archives,
228                        gcda_stem_archives,
229                        profdatas,
230                        profraws,
231                        infos,
232                        xmls,
233                        gocovs,
234                        linked_files_maps,
235                        is_llvm,
236                    );
237                }
238            }
239        }
240    }
241
242    pub fn read(&self, name: &str) -> Option<Vec<u8>> {
243        match *self.item.borrow_mut() {
244            ArchiveType::Zip(ref mut zip) => {
245                let mut zip = zip.borrow_mut();
246                let zipfile = zip.by_name(name);
247                match zipfile {
248                    Ok(mut f) => {
249                        let mut buf = Vec::with_capacity(f.size() as usize + 1);
250                        f.read_to_end(&mut buf).expect("Failed to read gcda file");
251                        Some(buf)
252                    }
253                    Err(_) => None,
254                }
255            }
256            ArchiveType::Dir(ref dir) => {
257                let path = dir.join(name);
258                if let Ok(metadata) = fs::metadata(&path) {
259                    match File::open(path) {
260                        Ok(mut f) => {
261                            let mut buf = Vec::with_capacity(metadata.len() as usize + 1);
262                            f.read_to_end(&mut buf).expect("Failed to read gcda file");
263                            Some(buf)
264                        }
265                        Err(_) => None,
266                    }
267                } else {
268                    None
269                }
270            }
271            ArchiveType::Plain(_) => {
272                if let Ok(metadata) = fs::metadata(name) {
273                    match File::open(name) {
274                        Ok(mut f) => {
275                            let mut buf = Vec::with_capacity(metadata.len() as usize + 1);
276                            f.read_to_end(&mut buf)
277                                .unwrap_or_else(|_| panic!("Failed to read file: {}.", name));
278                            Some(buf)
279                        }
280                        Err(_) => None,
281                    }
282                } else {
283                    None
284                }
285            }
286        }
287    }
288
289    pub fn extract(&self, name: &str, path: &Path) -> bool {
290        let dest_parent = path.parent().unwrap();
291        if !dest_parent.exists() {
292            fs::create_dir_all(dest_parent).expect("Cannot create parent directory");
293        }
294
295        match *self.item.borrow_mut() {
296            ArchiveType::Zip(ref mut zip) => {
297                let mut zip = zip.borrow_mut();
298                let zipfile = zip.by_name(name);
299                if let Ok(mut f) = zipfile {
300                    let mut file = File::create(path).expect("Failed to create file");
301                    io::copy(&mut f, &mut file).expect("Failed to copy file from ZIP");
302                    true
303                } else {
304                    false
305                }
306            }
307            ArchiveType::Dir(ref dir) => {
308                // don't use a hard link here because it can fail when src and dst are not on the same device
309                let src_path = dir.join(name);
310
311                crate::symlink::symlink_file(&src_path, path).unwrap_or_else(|_| {
312                    panic!("Failed to create a symlink {:?} -> {:?}", src_path, path)
313                });
314                true
315            }
316            ArchiveType::Plain(_) => {
317                panic!("We shouldn't be there !!");
318            }
319        }
320    }
321}
322
323fn gcno_gcda_producer(
324    tmp_dir: &Path,
325    gcno_stem_archives: &FxHashMap<GCNOStem, &Archive>,
326    gcda_stem_archives: &FxHashMap<String, Vec<&Archive>>,
327    sender: &JobSender,
328    ignore_orphan_gcno: bool,
329) {
330    let send_job = |item, name| {
331        sender
332            .send(Some(WorkItem {
333                format: ItemFormat::Gcno,
334                item,
335                name,
336            }))
337            .unwrap()
338    };
339
340    for (gcno_stem, gcno_archive) in gcno_stem_archives {
341        let stem = &gcno_stem.stem;
342        if let Some(gcda_archives) = gcda_stem_archives.get(stem) {
343            let gcno_archive = *gcno_archive;
344            let gcno = format!("{stem}.gcno").to_string();
345            let physical_gcno_path = tmp_dir.join(format!("{}_{}.gcno", stem, 1));
346            if gcno_stem.llvm {
347                let mut gcda_buffers: Vec<Vec<u8>> = Vec::with_capacity(gcda_archives.len());
348                if let Some(gcno_buffer) = gcno_archive.read(&gcno) {
349                    for gcda_archive in gcda_archives {
350                        let gcda = format!("{stem}.gcda").to_string();
351                        if let Some(gcda_buf) = gcda_archive.read(&gcda) {
352                            gcda_buffers.push(gcda_buf);
353                        }
354                    }
355                    send_job(
356                        ItemType::Buffers(GcnoBuffers {
357                            stem: stem.clone(),
358                            gcno_buf: gcno_buffer,
359                            gcda_buf: gcda_buffers,
360                        }),
361                        "".to_string(),
362                    );
363                }
364            } else {
365                gcno_archive.extract(&gcno, &physical_gcno_path);
366                for (num, &gcda_archive) in gcda_archives.iter().enumerate() {
367                    let gcno_path = tmp_dir.join(format!("{}_{}.gcno", stem, num + 1));
368                    let gcda = format!("{stem}.gcda").to_string();
369
370                    // Create symlinks.
371                    if num != 0 {
372                        fs::hard_link(&physical_gcno_path, &gcno_path).unwrap_or_else(|_| {
373                            panic!("Failed to create hardlink {:?}", gcno_path)
374                        });
375                    }
376
377                    let gcda_path = tmp_dir.join(format!("{}_{}.gcda", stem, num + 1));
378                    if gcda_archive.extract(&gcda, &gcda_path) || (num == 0 && !ignore_orphan_gcno)
379                    {
380                        send_job(
381                            ItemType::Path((stem.clone(), gcno_path)),
382                            gcda_archive.get_name().to_string(),
383                        );
384                    }
385                }
386            }
387        } else if !ignore_orphan_gcno {
388            let gcno_archive = *gcno_archive;
389            let gcno = format!("{stem}.gcno").to_string();
390            if gcno_stem.llvm {
391                if let Some(gcno_buf) = gcno_archive.read(&gcno) {
392                    send_job(
393                        ItemType::Buffers(GcnoBuffers {
394                            stem: stem.clone(),
395                            gcno_buf,
396                            gcda_buf: Vec::new(),
397                        }),
398                        gcno_archive.get_name().to_string(),
399                    );
400                }
401            } else {
402                let physical_gcno_path = tmp_dir.join(format!("{}_{}.gcno", stem, 1));
403                if gcno_archive.extract(&gcno, &physical_gcno_path) {
404                    send_job(
405                        ItemType::Path((stem.clone(), physical_gcno_path)),
406                        gcno_archive.get_name().to_string(),
407                    );
408                }
409            }
410        }
411    }
412}
413
414fn llvm_format_producer(
415    tmp_dir: &Path,
416    profiles: &FxHashMap<String, Vec<&Archive>>,
417    item_format: ItemFormat,
418    sender: &JobSender,
419) {
420    let ext = match item_format {
421        ItemFormat::Profdata => "profdata",
422        ItemFormat::Profraw => "profraw",
423        _ => panic!("function is specific to profdata and profraw files"),
424    };
425
426    if profiles.is_empty() {
427        return;
428    }
429
430    let mut profile_paths = Vec::new();
431
432    for (name, archives) in profiles {
433        let path = PathBuf::from(name);
434        let stem = clean_path(&path.with_extension(""));
435
436        // TODO: If there is only one archive and it is not a zip, we don't need to "extract".
437
438        for (num, &archive) in archives.iter().enumerate() {
439            let profile_path = if let ArchiveType::Plain(_) = *archive.item.borrow() {
440                Some(path.clone())
441            } else {
442                None
443            };
444
445            let profile_path = if let Some(profile_path) = profile_path {
446                profile_path
447            } else {
448                let tmp_path = tmp_dir.join(format!("{}_{}.{}", stem, num + 1, ext));
449                archive.extract(name, &tmp_path);
450                tmp_path
451            };
452
453            profile_paths.push(profile_path);
454        }
455    }
456
457    sender
458        .send(Some(WorkItem {
459            format: item_format,
460            item: ItemType::Paths(profile_paths),
461            name: ext.to_string(),
462        }))
463        .unwrap()
464}
465
466fn file_content_producer(
467    files: &FxHashMap<String, Vec<&Archive>>,
468    sender: &JobSender,
469    item_format: ItemFormat,
470) {
471    for (name, archives) in files {
472        for archive in archives {
473            if let Some(buffer) = archive.read(name) {
474                sender
475                    .send(Some(WorkItem {
476                        format: item_format,
477                        item: ItemType::Content(buffer),
478                        name: archive.get_name().to_string(),
479                    }))
480                    .unwrap();
481            }
482        }
483    }
484}
485
486pub fn get_mapping(linked_files_maps: &FxHashMap<String, &Archive>) -> Option<Vec<u8>> {
487    if let Some((name, archive)) = linked_files_maps.iter().next() {
488        archive.read(name)
489    } else {
490        None
491    }
492}
493
494fn open_archive(path: &str) -> ZipArchive<BufReader<File>> {
495    let file = File::open(path).unwrap_or_else(|_| panic!("Failed to open ZIP file '{}'.", path));
496    let reader = BufReader::new(file);
497    ZipArchive::new(reader).unwrap_or_else(|_| panic!("Failed to parse ZIP file: {}", path))
498}
499
500pub fn producer(
501    tmp_dir: &Path,
502    paths: &[String],
503    sender: &JobSender,
504    ignore_orphan_gcno: bool,
505    is_llvm: bool,
506) -> Option<Vec<u8>> {
507    let mut archives: Vec<Archive> = Vec::new();
508    let mut plain_files: Vec<PathBuf> = Vec::new();
509
510    let current_dir = env::current_dir().unwrap();
511
512    for path in paths {
513        if path.ends_with(".zip") {
514            let archive = open_archive(path);
515            archives.push(Archive {
516                name: path.to_string(),
517                item: RefCell::new(ArchiveType::Zip(RefCell::new(archive))),
518            });
519        } else {
520            let path_dir = PathBuf::from(path);
521            let full_path = if path_dir.is_relative() {
522                current_dir.join(path_dir)
523            } else {
524                path_dir
525            };
526            if full_path.is_dir() {
527                archives.push(Archive {
528                    name: path.to_string(),
529                    item: RefCell::new(ArchiveType::Dir(full_path)),
530                });
531            } else if let Some(ext) = full_path.clone().extension() {
532                let ext = ext.to_str().unwrap();
533                if ext == "info"
534                    || ext == "json"
535                    || ext == "xml"
536                    || ext == "profraw"
537                    || ext == "profdata"
538                    || ext == "out"
539                    || ext == "dat"
540                {
541                    plain_files.push(full_path);
542                } else {
543                    panic!(
544                        "Cannot load file '{:?}': it isn't a .info, a .json, a .out, a .xml, or a .dat file.",
545                        full_path
546                    );
547                }
548            } else {
549                panic!("Cannot load file '{:?}': it isn't a directory, a .info, a .json, a .out, a .xml, or a .dat file.", full_path);
550            }
551        }
552    }
553
554    if !plain_files.is_empty() {
555        archives.push(Archive {
556            name: "plain files".to_string(),
557            item: RefCell::new(ArchiveType::Plain(plain_files)),
558        });
559    }
560
561    let gcno_stems_archives: RefCell<FxHashMap<GCNOStem, &Archive>> =
562        RefCell::new(FxHashMap::default());
563    let gcda_stems_archives: RefCell<FxHashMap<String, Vec<&Archive>>> =
564        RefCell::new(FxHashMap::default());
565    let profdatas: RefCell<FxHashMap<String, Vec<&Archive>>> = RefCell::new(FxHashMap::default());
566    let profraws: RefCell<FxHashMap<String, Vec<&Archive>>> = RefCell::new(FxHashMap::default());
567    let infos: RefCell<FxHashMap<String, Vec<&Archive>>> = RefCell::new(FxHashMap::default());
568    let xmls: RefCell<FxHashMap<String, Vec<&Archive>>> = RefCell::new(FxHashMap::default());
569    let gocovs: RefCell<FxHashMap<String, Vec<&Archive>>> = RefCell::new(FxHashMap::default());
570
571    let linked_files_maps: RefCell<FxHashMap<String, &Archive>> =
572        RefCell::new(FxHashMap::default());
573
574    for archive in &mut archives {
575        archive.explore(
576            &gcno_stems_archives,
577            &gcda_stems_archives,
578            &profdatas,
579            &profraws,
580            &infos,
581            &xmls,
582            &gocovs,
583            &linked_files_maps,
584            is_llvm,
585        );
586    }
587
588    assert!(
589        !(gcno_stems_archives.borrow().is_empty()
590            && profdatas.borrow().is_empty()
591            && profraws.borrow().is_empty()
592            && infos.borrow().is_empty()
593            && gocovs.borrow().is_empty()
594            && xmls.borrow().is_empty()),
595        "No input files found"
596    );
597
598    file_content_producer(&infos.into_inner(), sender, ItemFormat::Info);
599    file_content_producer(&xmls.into_inner(), sender, ItemFormat::JacocoXml);
600    file_content_producer(&gocovs.into_inner(), sender, ItemFormat::Gocov);
601    llvm_format_producer(
602        tmp_dir,
603        &profdatas.into_inner(),
604        ItemFormat::Profdata,
605        sender,
606    );
607    llvm_format_producer(tmp_dir, &profraws.into_inner(), ItemFormat::Profraw, sender);
608    gcno_gcda_producer(
609        tmp_dir,
610        &gcno_stems_archives.into_inner(),
611        &gcda_stems_archives.into_inner(),
612        sender,
613        ignore_orphan_gcno,
614    );
615
616    get_mapping(&linked_files_maps.into_inner())
617}
618
619#[cfg(test)]
620mod tests {
621    use super::*;
622    use crossbeam_channel::unbounded;
623    use serde_json::{self, Value};
624
625    fn check_produced(
626        directory: PathBuf,
627        receiver: &JobReceiver,
628        expected: Vec<(ItemFormat, bool, &str, bool)>,
629    ) {
630        let mut vec: Vec<Option<WorkItem>> = Vec::new();
631
632        while let Ok(elem) = receiver.try_recv() {
633            vec.push(elem);
634        }
635
636        for elem in &expected {
637            assert!(
638                vec.iter().any(|x| {
639                    if !x.is_some() {
640                        return false;
641                    }
642
643                    let x = x.as_ref().unwrap();
644
645                    if x.format != elem.0 {
646                        return false;
647                    }
648
649                    match x.item {
650                        ItemType::Content(_) => !elem.1,
651                        ItemType::Path((_, ref p)) => elem.1 && p.ends_with(elem.2),
652                        ItemType::Paths(ref paths) => paths.iter().any(|p| p.ends_with(elem.2)),
653                        ItemType::Buffers(ref b) => b.stem.replace('\\', "/").ends_with(elem.2),
654                    }
655                }),
656                "Missing {:?}",
657                elem
658            );
659        }
660
661        for v in &vec {
662            let v = v.as_ref().unwrap();
663            assert!(
664                expected.iter().any(|x| {
665                    if v.format != x.0 {
666                        return false;
667                    }
668
669                    match v.item {
670                        ItemType::Content(_) => !x.1,
671                        ItemType::Path((_, ref p)) => x.1 && p.ends_with(x.2),
672                        ItemType::Paths(ref paths) => paths.iter().any(|p| p.ends_with(x.2)),
673                        ItemType::Buffers(ref b) => b.stem.replace('\\', "/").ends_with(x.2),
674                    }
675                }),
676                "Unexpected {:?}",
677                v
678            );
679        }
680
681        // Make sure we haven't generated duplicated entries.
682        assert!(vec.len() <= expected.len());
683
684        // Assert file exists and file with the same name but with extension .gcda exists.
685        for x in expected.iter() {
686            if !x.1 {
687                continue;
688            }
689
690            let p = directory.join(x.2);
691            assert!(p.exists(), "{} doesn't exist", p.display());
692            if x.0 == ItemFormat::Gcno {
693                let gcda =
694                    p.with_file_name(format!("{}.gcda", p.file_stem().unwrap().to_str().unwrap()));
695                if x.3 {
696                    assert!(gcda.exists(), "{} doesn't exist", gcda.display());
697                } else {
698                    assert!(!gcda.exists(), "{} exists", gcda.display());
699                }
700            }
701        }
702    }
703
704    #[test]
705    fn test_dir_producer() {
706        let (sender, receiver) = unbounded();
707
708        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
709        let tmp_path = tmp_dir.path().to_owned();
710        let mapping = producer(&tmp_path, &["test".to_string()], &sender, false, false);
711
712        let expected = vec![
713            (ItemFormat::Gcno, true, "Platform_1.gcno", true),
714            (
715                ItemFormat::Gcno,
716                true,
717                "sub2/RootAccessibleWrap_1.gcno",
718                true,
719            ),
720            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
721            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
722            (
723                ItemFormat::Gcno,
724                true,
725                "nsMaiInterfaceDocument_1.gcno",
726                true,
727            ),
728            (
729                ItemFormat::Gcno,
730                true,
731                "Unified_cpp_netwerk_base0_1.gcno",
732                true,
733            ),
734            (ItemFormat::Gcno, true, "prova_1.gcno", true),
735            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
736            (ItemFormat::Gcno, true, "negative_counts_1.gcno", true),
737            (ItemFormat::Gcno, true, "64bit_count_1.gcno", true),
738            (ItemFormat::Gcno, true, "no_gcda/main_1.gcno", false),
739            (ItemFormat::Gcno, true, "only_one_gcda/main_1.gcno", true),
740            (ItemFormat::Gcno, true, "only_one_gcda/orphan_1.gcno", false),
741            (
742                ItemFormat::Gcno,
743                true,
744                "gcno_symlink/gcda/main_1.gcno",
745                true,
746            ),
747            (
748                ItemFormat::Gcno,
749                true,
750                "gcno_symlink/gcno/main_1.gcno",
751                false,
752            ),
753            (
754                ItemFormat::Gcno,
755                false,
756                "rust/generics_with_two_parameters",
757                true,
758            ),
759            (ItemFormat::Gcno, true, "reader_gcc-6_1.gcno", true),
760            (ItemFormat::Gcno, true, "reader_gcc-7_1.gcno", true),
761            (ItemFormat::Gcno, true, "reader_gcc-8_1.gcno", true),
762            (ItemFormat::Gcno, true, "reader_gcc-9_1.gcno", true),
763            (ItemFormat::Gcno, true, "reader_gcc-10_1.gcno", true),
764            (ItemFormat::Gcno, true, "reader_clang-22_1.gcno", true),
765            (ItemFormat::Info, false, "1494603973-2977-7.info", false),
766            (ItemFormat::Info, false, "prova.info", false),
767            (ItemFormat::Info, false, "prova_fn_with_commas.info", false),
768            (ItemFormat::Info, false, "empty_line.info", false),
769            (ItemFormat::Info, false, "invalid_DA_record.info", false),
770            (
771                ItemFormat::Info,
772                false,
773                "relative_path/relative_path.info",
774                false,
775            ),
776            (ItemFormat::Info, false, "dat/simple.dat", false),
777            (ItemFormat::Gcno, false, "llvm/file", true),
778            (ItemFormat::Gcno, false, "llvm/file_branch", true),
779            (ItemFormat::Gcno, false, "llvm/reader", true),
780            (
781                ItemFormat::JacocoXml,
782                false,
783                "jacoco/basic-jacoco.xml",
784                false,
785            ),
786            (
787                ItemFormat::JacocoXml,
788                false,
789                "jacoco/inner-classes.xml",
790                false,
791            ),
792            (
793                ItemFormat::JacocoXml,
794                false,
795                "jacoco/multiple-top-level-classes.xml",
796                false,
797            ),
798            (
799                ItemFormat::JacocoXml,
800                false,
801                "jacoco/full-junit4-report-multiple-top-level-classes.xml",
802                false,
803            ),
804            (
805                ItemFormat::JacocoXml,
806                false,
807                "jacoco/kotlin-jacoco-report.xml",
808                false,
809            ),
810            (ItemFormat::Profraw, true, "default_1.profraw", false),
811            (
812                ItemFormat::Gcno,
813                true,
814                "mozillavpn_serverconnection_1.gcno",
815                true,
816            ),
817            (ItemFormat::Gocov, false, "go.out", false),
818        ];
819
820        check_produced(tmp_path, &receiver, expected);
821        assert!(mapping.is_some());
822        let mapping: Value = serde_json::from_slice(&mapping.unwrap()).unwrap();
823        assert_eq!(
824            mapping
825                .get("dist/include/zlib.h")
826                .unwrap()
827                .as_str()
828                .unwrap(),
829            "modules/zlib/src/zlib.h"
830        );
831    }
832
833    #[test]
834    fn test_dir_producer_multiple_directories() {
835        let (sender, receiver) = unbounded();
836
837        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
838        let tmp_path = tmp_dir.path().to_owned();
839        let mapping = producer(
840            &tmp_path,
841            &["test/sub".to_string(), "test/sub2".to_string()],
842            &sender,
843            false,
844            false,
845        );
846
847        let expected = vec![
848            (ItemFormat::Gcno, true, "RootAccessibleWrap_1.gcno", true),
849            (ItemFormat::Gcno, true, "prova2_1.gcno", true),
850        ];
851
852        check_produced(tmp_path, &receiver, expected);
853        assert!(mapping.is_none());
854    }
855
856    #[test]
857    fn test_dir_producer_directory_with_gcno_symlinks() {
858        let (sender, receiver) = unbounded();
859
860        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
861        let tmp_path = tmp_dir.path().to_owned();
862        let mapping = producer(
863            &tmp_path,
864            &["test/gcno_symlink/gcda".to_string()],
865            &sender,
866            false,
867            false,
868        );
869
870        let expected = vec![(ItemFormat::Gcno, true, "main_1.gcno", true)];
871
872        check_produced(tmp_path, &receiver, expected);
873        assert!(mapping.is_none());
874    }
875
876    #[test]
877    fn test_dir_producer_directory_with_no_gcda() {
878        let (sender, receiver) = unbounded();
879
880        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
881        let tmp_path = tmp_dir.path().to_owned();
882        let mapping = producer(
883            &tmp_path,
884            &["test/only_one_gcda".to_string()],
885            &sender,
886            false,
887            false,
888        );
889
890        let expected = vec![
891            (ItemFormat::Gcno, true, "main_1.gcno", true),
892            (ItemFormat::Gcno, true, "orphan_1.gcno", false),
893        ];
894
895        check_produced(tmp_path, &receiver, expected);
896        assert!(mapping.is_none());
897    }
898
899    #[test]
900    fn test_dir_producer_directory_with_no_gcda_ignore_orphan_gcno() {
901        let (sender, receiver) = unbounded();
902
903        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
904        let tmp_path = tmp_dir.path().to_owned();
905        let mapping = producer(
906            &tmp_path,
907            &["test/only_one_gcda".to_string()],
908            &sender,
909            true,
910            false,
911        );
912
913        let expected = vec![(ItemFormat::Gcno, true, "main_1.gcno", true)];
914
915        check_produced(tmp_path, &receiver, expected);
916        assert!(mapping.is_none());
917    }
918
919    #[test]
920    fn test_zip_producer_with_gcda_dir() {
921        let (sender, receiver) = unbounded();
922
923        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
924        let tmp_path = tmp_dir.path().to_owned();
925        let mapping = producer(
926            &tmp_path,
927            &[
928                "test/zip_dir/gcno.zip".to_string(),
929                "test/zip_dir".to_string(),
930            ],
931            &sender,
932            false,
933            false,
934        );
935
936        let expected = vec![
937            (ItemFormat::Gcno, true, "Platform_1.gcno", true),
938            (
939                ItemFormat::Gcno,
940                true,
941                "sub2/RootAccessibleWrap_1.gcno",
942                true,
943            ),
944            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
945            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
946            (
947                ItemFormat::Gcno,
948                true,
949                "nsMaiInterfaceDocument_1.gcno",
950                true,
951            ),
952            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
953        ];
954
955        check_produced(tmp_path, &receiver, expected);
956        assert!(mapping.is_some());
957        let mapping: Value = serde_json::from_slice(&mapping.unwrap()).unwrap();
958        assert_eq!(
959            mapping
960                .get("dist/include/zlib.h")
961                .unwrap()
962                .as_str()
963                .unwrap(),
964            "modules/zlib/src/zlib.h"
965        );
966    }
967
968    // Test extracting multiple gcda archives.
969    #[test]
970    fn test_zip_producer_multiple_gcda_archives() {
971        let (sender, receiver) = unbounded();
972
973        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
974        let tmp_path = tmp_dir.path().to_owned();
975        let mapping = producer(
976            &tmp_path,
977            &[
978                "test/gcno.zip".to_string(),
979                "test/gcda1.zip".to_string(),
980                "test/gcda2.zip".to_string(),
981            ],
982            &sender,
983            false,
984            false,
985        );
986
987        let expected = vec![
988            (ItemFormat::Gcno, true, "Platform_1.gcno", true),
989            (
990                ItemFormat::Gcno,
991                true,
992                "sub2/RootAccessibleWrap_1.gcno",
993                true,
994            ),
995            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
996            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
997            (
998                ItemFormat::Gcno,
999                true,
1000                "nsMaiInterfaceDocument_1.gcno",
1001                true,
1002            ),
1003            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1004            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_2.gcno", true),
1005            (
1006                ItemFormat::Gcno,
1007                true,
1008                "nsMaiInterfaceDocument_2.gcno",
1009                true,
1010            ),
1011            (ItemFormat::Gcno, true, "nsGnomeModule_2.gcno", true),
1012            (ItemFormat::Gcno, true, "sub/prova2_2.gcno", true),
1013        ];
1014
1015        check_produced(tmp_path, &receiver, expected);
1016        assert!(mapping.is_some());
1017        let mapping: Value = serde_json::from_slice(&mapping.unwrap()).unwrap();
1018        assert_eq!(
1019            mapping
1020                .get("dist/include/zlib.h")
1021                .unwrap()
1022                .as_str()
1023                .unwrap(),
1024            "modules/zlib/src/zlib.h"
1025        );
1026    }
1027
1028    // Test extracting gcno with no path mapping.
1029    #[test]
1030    fn test_zip_producer_gcno_with_no_path_mapping() {
1031        let (sender, receiver) = unbounded();
1032
1033        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1034        let tmp_path = tmp_dir.path().to_owned();
1035        let mapping = producer(
1036            &tmp_path,
1037            &[
1038                "test/gcno_no_path_mapping.zip".to_string(),
1039                "test/gcda1.zip".to_string(),
1040            ],
1041            &sender,
1042            false,
1043            false,
1044        );
1045
1046        let expected = vec![
1047            (ItemFormat::Gcno, true, "Platform_1.gcno", true),
1048            (
1049                ItemFormat::Gcno,
1050                true,
1051                "sub2/RootAccessibleWrap_1.gcno",
1052                true,
1053            ),
1054            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
1055            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
1056            (
1057                ItemFormat::Gcno,
1058                true,
1059                "nsMaiInterfaceDocument_1.gcno",
1060                true,
1061            ),
1062            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1063        ];
1064
1065        check_produced(tmp_path, &receiver, expected);
1066        assert!(mapping.is_none());
1067    }
1068
1069    // Test calling zip_producer with a different order of zip files.
1070    #[test]
1071    fn test_zip_producer_different_order_of_zip_files() {
1072        let (sender, receiver) = unbounded();
1073
1074        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1075        let tmp_path = tmp_dir.path().to_owned();
1076        producer(
1077            &tmp_path,
1078            &[
1079                "test/gcda1.zip".to_string(),
1080                "test/gcno.zip".to_string(),
1081                "test/gcda2.zip".to_string(),
1082            ],
1083            &sender,
1084            false,
1085            false,
1086        );
1087
1088        let expected = vec![
1089            (ItemFormat::Gcno, true, "Platform_1.gcno", true),
1090            (
1091                ItemFormat::Gcno,
1092                true,
1093                "sub2/RootAccessibleWrap_1.gcno",
1094                true,
1095            ),
1096            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
1097            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
1098            (
1099                ItemFormat::Gcno,
1100                true,
1101                "nsMaiInterfaceDocument_1.gcno",
1102                true,
1103            ),
1104            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1105            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_2.gcno", true),
1106            (
1107                ItemFormat::Gcno,
1108                true,
1109                "nsMaiInterfaceDocument_2.gcno",
1110                true,
1111            ),
1112            (ItemFormat::Gcno, true, "nsGnomeModule_2.gcno", true),
1113            (ItemFormat::Gcno, true, "sub/prova2_2.gcno", true),
1114        ];
1115
1116        check_produced(tmp_path, &receiver, expected);
1117    }
1118
1119    // Test extracting profraw files.
1120    #[test]
1121    fn test_zip_producer_profraw_files() {
1122        let (sender, receiver) = unbounded();
1123
1124        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1125        let tmp_path = tmp_dir.path().to_owned();
1126        producer(
1127            &tmp_path,
1128            &[
1129                "test/profraw1.zip".to_string(),
1130                "test/profraw2.zip".to_string(),
1131            ],
1132            &sender,
1133            false,
1134            false,
1135        );
1136
1137        let expected = vec![
1138            (ItemFormat::Profraw, true, "default_1.profraw", false),
1139            (ItemFormat::Profraw, true, "default_2.profraw", false),
1140        ];
1141
1142        check_produced(tmp_path, &receiver, expected);
1143    }
1144
1145    // Test extracting info files.
1146    #[test]
1147    fn test_zip_producer_info_files() {
1148        let (sender, receiver) = unbounded();
1149
1150        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1151        let tmp_path = tmp_dir.path().to_owned();
1152        producer(
1153            &tmp_path,
1154            &["test/info1.zip".to_string(), "test/info2.zip".to_string()],
1155            &sender,
1156            false,
1157            false,
1158        );
1159
1160        let expected = vec![
1161            (ItemFormat::Info, false, "1494603967-2977-2_0.info", true),
1162            (ItemFormat::Info, false, "1494603967-2977-3_0.info", true),
1163            (ItemFormat::Info, false, "1494603967-2977-4_0.info", true),
1164            (ItemFormat::Info, false, "1494603968-2977-5_0.info", true),
1165            (ItemFormat::Info, false, "1494603972-2977-6_0.info", true),
1166            (ItemFormat::Info, false, "1494603973-2977-7_0.info", true),
1167            (ItemFormat::Info, false, "1494603967-2977-2_1.info", true),
1168            (ItemFormat::Info, false, "1494603967-2977-3_1.info", true),
1169            (ItemFormat::Info, false, "1494603967-2977-4_1.info", true),
1170            (ItemFormat::Info, false, "1494603968-2977-5_1.info", true),
1171            (ItemFormat::Info, false, "1494603972-2977-6_1.info", true),
1172            (ItemFormat::Info, false, "1494603973-2977-7_1.info", true),
1173        ];
1174
1175        check_produced(tmp_path, &receiver, expected);
1176    }
1177
1178    // Test extracting jacoco report XML files.
1179    #[test]
1180    fn test_zip_producer_jacoco_xml_files() {
1181        let (sender, receiver) = unbounded();
1182
1183        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1184        let tmp_path = tmp_dir.path().to_owned();
1185        producer(
1186            &tmp_path,
1187            &[
1188                "test/jacoco1.zip".to_string(),
1189                "test/jacoco2.zip".to_string(),
1190            ],
1191            &sender,
1192            false,
1193            false,
1194        );
1195
1196        let expected = vec![
1197            (
1198                ItemFormat::JacocoXml,
1199                false,
1200                "jacoco/basic-jacoco.xml",
1201                true,
1202            ),
1203            (ItemFormat::JacocoXml, false, "inner-classes.xml", true),
1204        ];
1205
1206        check_produced(tmp_path, &receiver, expected);
1207    }
1208
1209    // Test extracting both jacoco xml and info files.
1210    #[test]
1211    fn test_zip_producer_both_info_and_jacoco_xml() {
1212        let (sender, receiver) = unbounded();
1213
1214        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1215        let tmp_path = tmp_dir.path().to_owned();
1216        producer(
1217            &tmp_path,
1218            &[
1219                "test/jacoco1.zip".to_string(),
1220                "test/jacoco2.zip".to_string(),
1221                "test/info1.zip".to_string(),
1222                "test/info2.zip".to_string(),
1223            ],
1224            &sender,
1225            false,
1226            false,
1227        );
1228
1229        let expected = vec![
1230            (
1231                ItemFormat::JacocoXml,
1232                false,
1233                "jacoco/basic-jacoco.xml",
1234                true,
1235            ),
1236            (ItemFormat::JacocoXml, false, "inner-classes.xml", true),
1237            (ItemFormat::Info, false, "1494603967-2977-2_0.info", true),
1238            (ItemFormat::Info, false, "1494603967-2977-3_0.info", true),
1239            (ItemFormat::Info, false, "1494603967-2977-4_0.info", true),
1240            (ItemFormat::Info, false, "1494603968-2977-5_0.info", true),
1241            (ItemFormat::Info, false, "1494603972-2977-6_0.info", true),
1242            (ItemFormat::Info, false, "1494603973-2977-7_0.info", true),
1243            (ItemFormat::Info, false, "1494603967-2977-2_1.info", true),
1244            (ItemFormat::Info, false, "1494603967-2977-3_1.info", true),
1245            (ItemFormat::Info, false, "1494603967-2977-4_1.info", true),
1246            (ItemFormat::Info, false, "1494603968-2977-5_1.info", true),
1247            (ItemFormat::Info, false, "1494603972-2977-6_1.info", true),
1248            (ItemFormat::Info, false, "1494603973-2977-7_1.info", true),
1249        ];
1250
1251        check_produced(tmp_path, &receiver, expected);
1252    }
1253
1254    // Test extracting both info and gcno/gcda files.
1255    #[test]
1256    fn test_zip_producer_both_info_and_gcnogcda_files() {
1257        let (sender, receiver) = unbounded();
1258
1259        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1260        let tmp_path = tmp_dir.path().to_owned();
1261        producer(
1262            &tmp_path,
1263            &[
1264                "test/gcno.zip".to_string(),
1265                "test/gcda1.zip".to_string(),
1266                "test/info1.zip".to_string(),
1267                "test/info2.zip".to_string(),
1268            ],
1269            &sender,
1270            false,
1271            false,
1272        );
1273
1274        let expected = vec![
1275            (ItemFormat::Gcno, true, "Platform_1.gcno", true),
1276            (
1277                ItemFormat::Gcno,
1278                true,
1279                "sub2/RootAccessibleWrap_1.gcno",
1280                true,
1281            ),
1282            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
1283            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
1284            (
1285                ItemFormat::Gcno,
1286                true,
1287                "nsMaiInterfaceDocument_1.gcno",
1288                true,
1289            ),
1290            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1291            (ItemFormat::Info, false, "1494603967-2977-2_0.info", true),
1292            (ItemFormat::Info, false, "1494603967-2977-3_0.info", true),
1293            (ItemFormat::Info, false, "1494603967-2977-4_0.info", true),
1294            (ItemFormat::Info, false, "1494603968-2977-5_0.info", true),
1295            (ItemFormat::Info, false, "1494603972-2977-6_0.info", true),
1296            (ItemFormat::Info, false, "1494603973-2977-7_0.info", true),
1297            (ItemFormat::Info, false, "1494603967-2977-2_1.info", true),
1298            (ItemFormat::Info, false, "1494603967-2977-3_1.info", true),
1299            (ItemFormat::Info, false, "1494603967-2977-4_1.info", true),
1300            (ItemFormat::Info, false, "1494603968-2977-5_1.info", true),
1301            (ItemFormat::Info, false, "1494603972-2977-6_1.info", true),
1302            (ItemFormat::Info, false, "1494603973-2977-7_1.info", true),
1303        ];
1304
1305        check_produced(tmp_path, &receiver, expected);
1306    }
1307
1308    // Test extracting gcno with no associated gcda.
1309    #[test]
1310    fn test_zip_producer_gcno_with_no_associated_gcda() {
1311        let (sender, receiver) = unbounded();
1312
1313        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1314        let tmp_path = tmp_dir.path().to_owned();
1315        let mapping = producer(
1316            &tmp_path,
1317            &[
1318                "test/no_gcda/main.gcno.zip".to_string(),
1319                "test/no_gcda/empty.gcda.zip".to_string(),
1320            ],
1321            &sender,
1322            false,
1323            false,
1324        );
1325
1326        let expected = vec![(ItemFormat::Gcno, true, "main_1.gcno", false)];
1327
1328        check_produced(tmp_path, &receiver, expected);
1329        assert!(mapping.is_none());
1330    }
1331
1332    // Test extracting gcno with an associated gcda file in only one zip file.
1333    #[test]
1334    fn test_zip_producer_gcno_with_associated_gcda_in_only_one_archive() {
1335        let (sender, receiver) = unbounded();
1336
1337        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1338        let tmp_path = tmp_dir.path().to_owned();
1339        let mapping = producer(
1340            &tmp_path,
1341            &[
1342                "test/no_gcda/main.gcno.zip".to_string(),
1343                "test/no_gcda/empty.gcda.zip".to_string(),
1344                "test/no_gcda/main.gcda.zip".to_string(),
1345            ],
1346            &sender,
1347            false,
1348            false,
1349        );
1350
1351        let expected = vec![(ItemFormat::Gcno, true, "main_1.gcno", true)];
1352
1353        check_produced(tmp_path, &receiver, expected);
1354        assert!(mapping.is_none());
1355    }
1356
1357    // Test passing a gcda archive with no gcno archive makes zip_producer fail.
1358    #[test]
1359    #[should_panic]
1360    fn test_zip_producer_with_gcda_archive_and_no_gcno_archive() {
1361        let (sender, _) = unbounded();
1362
1363        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1364        let tmp_path = tmp_dir.path().to_owned();
1365        producer(
1366            &tmp_path,
1367            &["test/no_gcda/main.gcda.zip".to_string()],
1368            &sender,
1369            false,
1370            false,
1371        );
1372    }
1373
1374    // Test extracting gcno/gcda archives, where a gcno file exist with no matching gcda file.
1375    #[test]
1376    fn test_zip_producer_no_matching_gcno() {
1377        let (sender, receiver) = unbounded();
1378
1379        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1380        let tmp_path = tmp_dir.path().to_owned();
1381        producer(
1382            &tmp_path,
1383            &["test/gcno.zip".to_string(), "test/gcda2.zip".to_string()],
1384            &sender,
1385            false,
1386            false,
1387        );
1388
1389        let expected = vec![
1390            (ItemFormat::Gcno, true, "Platform_1.gcno", false),
1391            (
1392                ItemFormat::Gcno,
1393                true,
1394                "sub2/RootAccessibleWrap_1.gcno",
1395                false,
1396            ),
1397            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
1398            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
1399            (
1400                ItemFormat::Gcno,
1401                true,
1402                "nsMaiInterfaceDocument_1.gcno",
1403                true,
1404            ),
1405            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1406        ];
1407
1408        check_produced(tmp_path, &receiver, expected);
1409    }
1410
1411    // Test extracting gcno/gcda archives, where a gcno file exist with no matching gcda file.
1412    // The gcno file should be produced only once, not twice.
1413    #[test]
1414    fn test_zip_producer_no_matching_gcno_two_gcda_archives() {
1415        let (sender, receiver) = unbounded();
1416
1417        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1418        let tmp_path = tmp_dir.path().to_owned();
1419        producer(
1420            &tmp_path,
1421            &[
1422                "test/gcno.zip".to_string(),
1423                "test/gcda2.zip".to_string(),
1424                "test/gcda2.zip".to_string(),
1425            ],
1426            &sender,
1427            false,
1428            false,
1429        );
1430
1431        let expected = vec![
1432            (ItemFormat::Gcno, true, "Platform_1.gcno", false),
1433            (
1434                ItemFormat::Gcno,
1435                true,
1436                "sub2/RootAccessibleWrap_1.gcno",
1437                false,
1438            ),
1439            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
1440            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_2.gcno", true),
1441            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
1442            (ItemFormat::Gcno, true, "sub/prova2_2.gcno", true),
1443            (
1444                ItemFormat::Gcno,
1445                true,
1446                "nsMaiInterfaceDocument_1.gcno",
1447                true,
1448            ),
1449            (
1450                ItemFormat::Gcno,
1451                true,
1452                "nsMaiInterfaceDocument_2.gcno",
1453                true,
1454            ),
1455            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1456            (ItemFormat::Gcno, true, "nsGnomeModule_2.gcno", true),
1457        ];
1458
1459        check_produced(tmp_path, &receiver, expected);
1460    }
1461
1462    // Test extracting gcno/gcda archives, where a gcno file exist with no matching gcda file and ignore orphan gcno files.
1463    #[test]
1464    fn test_zip_producer_no_matching_gcno_ignore_orphan_gcno() {
1465        let (sender, receiver) = unbounded();
1466
1467        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1468        let tmp_path = tmp_dir.path().to_owned();
1469        producer(
1470            &tmp_path,
1471            &["test/gcno.zip".to_string(), "test/gcda2.zip".to_string()],
1472            &sender,
1473            true,
1474            false,
1475        );
1476
1477        let expected = vec![
1478            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
1479            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
1480            (
1481                ItemFormat::Gcno,
1482                true,
1483                "nsMaiInterfaceDocument_1.gcno",
1484                true,
1485            ),
1486            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1487        ];
1488
1489        check_produced(tmp_path, &receiver, expected);
1490    }
1491
1492    // Test extracting gcno/gcda archives, where a gcno file exist with no matching gcda file and ignore orphan gcno files.
1493    #[test]
1494    fn test_zip_producer_no_matching_gcno_two_gcda_archives_ignore_orphan_gcno() {
1495        let (sender, receiver) = unbounded();
1496
1497        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1498        let tmp_path = tmp_dir.path().to_owned();
1499        producer(
1500            &tmp_path,
1501            &[
1502                "test/gcno.zip".to_string(),
1503                "test/gcda2.zip".to_string(),
1504                "test/gcda2.zip".to_string(),
1505            ],
1506            &sender,
1507            true,
1508            false,
1509        );
1510
1511        let expected = vec![
1512            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_1.gcno", true),
1513            (ItemFormat::Gcno, true, "nsMaiInterfaceValue_2.gcno", true),
1514            (ItemFormat::Gcno, true, "sub/prova2_1.gcno", true),
1515            (ItemFormat::Gcno, true, "sub/prova2_2.gcno", true),
1516            (
1517                ItemFormat::Gcno,
1518                true,
1519                "nsMaiInterfaceDocument_1.gcno",
1520                true,
1521            ),
1522            (
1523                ItemFormat::Gcno,
1524                true,
1525                "nsMaiInterfaceDocument_2.gcno",
1526                true,
1527            ),
1528            (ItemFormat::Gcno, true, "nsGnomeModule_1.gcno", true),
1529            (ItemFormat::Gcno, true, "nsGnomeModule_2.gcno", true),
1530        ];
1531
1532        check_produced(tmp_path, &receiver, expected);
1533    }
1534
1535    #[test]
1536    fn test_zip_producer_llvm_buffers() {
1537        let (sender, receiver) = unbounded();
1538
1539        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1540        let tmp_path = tmp_dir.path().to_owned();
1541        producer(
1542            &tmp_path,
1543            &[
1544                "test/llvm/gcno.zip".to_string(),
1545                "test/llvm/gcda1.zip".to_string(),
1546                "test/llvm/gcda2.zip".to_string(),
1547            ],
1548            &sender,
1549            true,
1550            true,
1551        );
1552        let gcno_buf: Vec<u8> = vec![
1553            111, 110, 99, 103, 42, 50, 48, 52, 74, 200, 254, 66, 0, 0, 0, 1, 9, 0, 0, 0, 0, 0, 0,
1554            0, 236, 217, 93, 255, 2, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 2, 0, 0, 0, 102, 105,
1555            108, 101, 46, 99, 0, 0, 1, 0, 0, 0, 0, 0, 65, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1556            0, 0, 0, 0, 0, 67, 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 1, 3,
1557            0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1558            0, 0, 0, 0, 0, 0, 0, 0, 69, 1, 8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 102,
1559            105, 108, 101, 46, 99, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1560            0,
1561        ];
1562        let gcda1_buf: Vec<u8> = vec![
1563            97, 100, 99, 103, 42, 50, 48, 52, 74, 200, 254, 66, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0,
1564            236, 217, 93, 255, 2, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 0, 0, 161, 1, 4, 0, 0, 0,
1565            1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 161, 9, 0, 0, 0, 0, 0, 0, 0,
1566            0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1567            0, 0, 0, 0, 0, 0, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1568        ];
1569        let gcda2_buf: Vec<u8> = vec![
1570            97, 100, 99, 103, 42, 50, 48, 52, 74, 200, 254, 66, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0,
1571            236, 217, 93, 255, 2, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 0, 0, 161, 1, 4, 0, 0, 0,
1572            1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 161, 9, 0, 0, 0, 0, 0, 0, 0,
1573            0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1574            0, 0, 0, 0, 0, 0, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1575        ];
1576
1577        while let Ok(elem) = receiver.try_recv() {
1578            let elem = elem.unwrap();
1579            if let ItemType::Buffers(buffers) = elem.item {
1580                let stem = PathBuf::from(buffers.stem);
1581                let stem = stem.file_stem().expect("Unable to get file_stem");
1582
1583                assert!(stem == "file", "Unexpected file: {:?}", stem);
1584                assert_eq!(buffers.gcno_buf, gcno_buf);
1585                assert_eq!(buffers.gcda_buf, vec![gcda1_buf.clone(), gcda2_buf.clone()]);
1586            } else {
1587                panic!("Buffers expected");
1588            }
1589        }
1590    }
1591
1592    #[test]
1593    fn test_plain_producer() {
1594        let (sender, receiver) = unbounded();
1595
1596        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1597        let tmp_path = tmp_dir.path().to_owned();
1598        let json_path = "test/linked-files-map.json";
1599        let mapping = producer(
1600            &tmp_path,
1601            &["test/prova.info".to_string(), json_path.to_string()],
1602            &sender,
1603            true,
1604            false,
1605        );
1606
1607        assert!(mapping.is_some());
1608        let mapping = mapping.unwrap();
1609
1610        let expected = vec![(ItemFormat::Info, false, "prova_1.info", true)];
1611
1612        if let Ok(mut reader) = File::open(json_path) {
1613            let mut json = Vec::new();
1614            reader.read_to_end(&mut json).unwrap();
1615            assert_eq!(json, mapping);
1616        } else {
1617            panic!("Failed to read the file: {}", json_path);
1618        }
1619
1620        check_produced(tmp_path, &receiver, expected);
1621    }
1622
1623    #[test]
1624    fn test_plain_profraw_producer() {
1625        let (sender, receiver) = unbounded();
1626
1627        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1628        let tmp_path = tmp_dir.path().to_owned();
1629        producer(
1630            &tmp_path,
1631            &["test/default.profraw".to_string()],
1632            &sender,
1633            true,
1634            false,
1635        );
1636
1637        let expected = vec![(ItemFormat::Profraw, true, "default.profraw", false)];
1638
1639        check_produced(PathBuf::from("test"), &receiver, expected);
1640    }
1641
1642    #[test]
1643    #[should_panic]
1644    fn test_plain_producer_with_gcno() {
1645        let (sender, _) = unbounded();
1646
1647        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1648        let tmp_path = tmp_dir.path().to_owned();
1649        producer(
1650            &tmp_path,
1651            &["sub2/RootAccessibleWrap_1.gcno".to_string()],
1652            &sender,
1653            true,
1654            false,
1655        );
1656    }
1657
1658    #[test]
1659    #[should_panic]
1660    fn test_plain_producer_with_gcda() {
1661        let (sender, _) = unbounded();
1662
1663        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1664        let tmp_path = tmp_dir.path().to_owned();
1665        producer(
1666            &tmp_path,
1667            &["./test/llvm/file.gcda".to_string()],
1668            &sender,
1669            true,
1670            false,
1671        );
1672    }
1673
1674    #[test]
1675    fn test_plain_producer_with_dat_file() {
1676        let (sender, receiver) = unbounded();
1677
1678        let tmp_dir = tempfile::tempdir().expect("Failed to create temporary directory");
1679        let tmp_path = tmp_dir.path().to_owned();
1680        producer(
1681            &tmp_path,
1682            &["test/dat/simple.dat".to_string()],
1683            &sender,
1684            false,
1685            false,
1686        );
1687
1688        let expected = vec![(ItemFormat::Info, false, "simple.dat", true)];
1689
1690        check_produced(tmp_path, &receiver, expected);
1691    }
1692
1693    #[test]
1694    fn test_jacoco_files() {
1695        let mut file = File::open("./test/jacoco/basic-report.xml").ok();
1696        assert!(
1697            Archive::check_file(file.as_mut(), &Archive::is_jacoco),
1698            "A Jacoco XML file expected"
1699        );
1700        let mut file =
1701            File::open("./test/jacoco/full-junit4-report-multiple-top-level-classes.xml").ok();
1702        assert!(
1703            Archive::check_file(file.as_mut(), &Archive::is_jacoco),
1704            "A Jacoco XML file expected"
1705        );
1706        let mut file = File::open("./test/jacoco/inner-classes.xml").ok();
1707        assert!(
1708            Archive::check_file(file.as_mut(), &Archive::is_jacoco),
1709            "A Jacoco XML file expected"
1710        );
1711        let mut file = File::open("./test/jacoco/multiple-top-level-classes.xml").ok();
1712        assert!(
1713            Archive::check_file(file.as_mut(), &Archive::is_jacoco),
1714            "A Jacoco XML file expected"
1715        );
1716        let mut file = File::open("./test/jacoco/not_jacoco_file.xml").ok();
1717        assert!(
1718            !Archive::check_file(file.as_mut(), &Archive::is_jacoco),
1719            "Not a Jacoco XML file expected"
1720        );
1721    }
1722
1723    #[test]
1724    fn test_info_files() {
1725        let mut file = File::open("./test/1494603973-2977-7.info").ok();
1726        assert!(
1727            Archive::check_file(file.as_mut(), &Archive::is_info),
1728            "An info file expected"
1729        );
1730        let mut file = File::open("./test/empty_line.info").ok();
1731        assert!(
1732            Archive::check_file(file.as_mut(), &Archive::is_info),
1733            "An info file expected"
1734        );
1735        let mut file = File::open("./test/relative_path/relative_path.info").ok();
1736        assert!(
1737            Archive::check_file(file.as_mut(), &Archive::is_info),
1738            "An info file expected"
1739        );
1740        let mut file = File::open("./test/not_info_file.info").ok();
1741        assert!(
1742            !Archive::check_file(file.as_mut(), &Archive::is_info),
1743            "Not an info file expected"
1744        );
1745    }
1746}