Squashed 'third_party/autocxx/' content from commit 629e8fa53
git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/integration-tests/tests/code_checkers.rs b/integration-tests/tests/code_checkers.rs
new file mode 100644
index 0000000..619c79c
--- /dev/null
+++ b/integration-tests/tests/code_checkers.rs
@@ -0,0 +1,182 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::{
+ fs::File,
+ io::{BufRead, BufReader},
+ path::PathBuf,
+};
+
+use itertools::{Either, Itertools};
+use proc_macro2::TokenStream;
+use quote::ToTokens;
+use syn::Item;
+
+use autocxx_integration_tests::{CodeChecker, CodeCheckerFns, TestError};
+
+/// Generates a closure which can be used to ensure that the given symbol
+/// is mentioned in the output and has documentation attached.
+/// The idea is that this is what we do in cases where we can't generate code properly.
+pub(crate) fn make_error_finder(error_symbol: &'static str) -> CodeChecker {
+ Box::new(ErrorFinder(error_symbol))
+}
+struct ErrorFinder(&'static str);
+
+impl CodeCheckerFns for ErrorFinder {
+ fn check_rust(&self, rs: syn::File) -> Result<(), TestError> {
+ let ffi_items = find_ffi_items(rs)?;
+ // Ensure there's some kind of struct entry for this symbol
+ let error_item = ffi_items
+ .into_iter()
+ .filter_map(|i| match i {
+ Item::Struct(its) if its.ident == self.0 => Some(its),
+ _ => None,
+ })
+ .next()
+ .ok_or_else(|| TestError::RsCodeExaminationFail("Couldn't find item".into()))?;
+ // Ensure doc attribute
+ error_item
+ .attrs
+ .into_iter()
+ .find(|a| a.path.get_ident().filter(|p| *p == "doc").is_some())
+ .ok_or_else(|| TestError::RsCodeExaminationFail("Item had no docs".into()))?;
+ Ok(())
+ }
+}
+
+fn find_ffi_items(f: syn::File) -> Result<Vec<Item>, TestError> {
+ let md = f
+ .items
+ .into_iter()
+ .filter_map(|i| match i {
+ Item::Mod(itm) => Some(itm),
+ _ => None,
+ })
+ .next()
+ .ok_or_else(|| TestError::RsCodeExaminationFail("No mods in file".into()))?;
+ let mut items = Vec::new();
+ find_all_non_mod_items(md, &mut items);
+ Ok(items)
+}
+
+fn find_all_non_mod_items(md: syn::ItemMod, items: &mut Vec<Item>) {
+ let (more_mods, mut these_items): (Vec<_>, Vec<_>) = md
+ .content
+ .into_iter()
+ .flat_map(|(_, more_items)| more_items.into_iter())
+ .partition_map(|i| match i {
+ Item::Mod(itm) => Either::Left(itm),
+ _ => Either::Right(i),
+ });
+ items.append(&mut these_items);
+ for md in more_mods.into_iter() {
+ find_all_non_mod_items(md, items);
+ }
+}
+
+struct StringFinder(Vec<String>);
+
+impl CodeCheckerFns for StringFinder {
+ fn check_rust(&self, rs: syn::File) -> Result<(), TestError> {
+ let toks = rs.to_token_stream().to_string();
+ for msg in &self.0 {
+ if !toks.contains(msg) {
+ return Err(TestError::RsCodeExaminationFail(format!(
+ "Couldn't find string '{}'",
+ msg
+ )));
+ };
+ }
+ Ok(())
+ }
+}
+
+/// Returns a code checker which simply hunts for a given string in the results
+pub(crate) fn make_string_finder(error_texts: Vec<String>) -> CodeChecker {
+ Box::new(StringFinder(error_texts))
+}
+
+struct RustCodeFinder(Vec<TokenStream>);
+
+impl CodeCheckerFns for RustCodeFinder {
+ fn check_rust(&self, rs: syn::File) -> Result<(), TestError> {
+ let haystack = rs.to_token_stream().to_string();
+ for msg in &self.0 {
+ let needle = msg.to_string();
+ if !haystack.contains(&needle) {
+ return Err(TestError::RsCodeExaminationFail(format!(
+ "Couldn't find tokens '{}'",
+ needle
+ )));
+ };
+ }
+ Ok(())
+ }
+}
+
+/// Returns a code checker which hunts for the given Rust tokens in the output
+pub(crate) fn make_rust_code_finder(code: Vec<TokenStream>) -> CodeChecker {
+ Box::new(RustCodeFinder(code))
+}
+
+/// Searches generated C++ for strings we want to find, or want _not_ to find,
+/// or both.
+pub(crate) struct CppMatcher<'a> {
+ positive_matches: &'a [&'a str],
+ negative_matches: &'a [&'a str],
+}
+
+impl<'a> CppMatcher<'a> {
+ pub(crate) fn new(positive_matches: &'a [&'a str], negative_matches: &'a [&'a str]) -> Self {
+ Self {
+ positive_matches,
+ negative_matches,
+ }
+ }
+}
+
+impl<'a> CodeCheckerFns for CppMatcher<'a> {
+ fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
+ let mut positives_needed = self.positive_matches.to_vec();
+ for filename in cpp {
+ let file = File::open(filename).unwrap();
+ let lines = BufReader::new(file).lines();
+ for l in lines.filter_map(|l| l.ok()) {
+ if self.negative_matches.iter().any(|neg| l.contains(neg)) {
+ return Err(TestError::CppCodeExaminationFail);
+ }
+ positives_needed.retain(|pos| !l.contains(pos));
+ }
+ }
+ if positives_needed.is_empty() {
+ Ok(())
+ } else {
+ Err(TestError::CppCodeExaminationFail)
+ }
+ }
+}
+
+pub(crate) struct NoSystemHeadersChecker;
+
+impl CodeCheckerFns for NoSystemHeadersChecker {
+ fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
+ for filename in cpp {
+ let file = File::open(filename).unwrap();
+ if BufReader::new(file)
+ .lines()
+ .any(|l| l.as_ref().unwrap().starts_with("#include <"))
+ {
+ return Err(TestError::CppCodeExaminationFail);
+ }
+ }
+ Ok(())
+ }
+ fn skip_build(&self) -> bool {
+ true
+ }
+}