Brian Silverman | 4e662aa | 2022-05-11 23:10:19 -0700 | [diff] [blame^] | 1 | // Copyright 2020 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| 6 | // option. This file may not be copied, modified, or distributed |
| 7 | // except according to those terms. |
| 8 | |
| 9 | use std::{convert::TryInto, fs::File, io::Write, path::Path}; |
| 10 | |
| 11 | use indexmap::map::IndexMap as HashMap; |
| 12 | |
| 13 | use assert_cmd::Command; |
| 14 | use autocxx_integration_tests::{build_from_folder, RsFindMode}; |
| 15 | use itertools::Itertools; |
| 16 | use tempdir::TempDir; |
| 17 | |
| 18 | static MAIN_RS: &str = concat!( |
| 19 | include_str!("../../../demo/src/main.rs"), |
| 20 | "#[link(name = \"autocxx-demo\")]\nextern \"C\" {}" |
| 21 | ); |
| 22 | static INPUT_H: &str = include_str!("../../../demo/src/input.h"); |
| 23 | static BLANK: &str = "// Blank autocxx placeholder"; |
| 24 | |
| 25 | static MAIN2_RS: &str = concat!( |
| 26 | include_str!("data/main2.rs"), |
| 27 | "#[link(name = \"autocxx-demo\")]\nextern \"C\" {}" |
| 28 | ); |
| 29 | static DIRECTIVE1_RS: &str = include_str!("data/directive1.rs"); |
| 30 | static DIRECTIVE2_RS: &str = include_str!("data/directive2.rs"); |
| 31 | static INPUT2_H: &str = include_str!("data/input2.h"); |
| 32 | static INPUT3_H: &str = include_str!("data/input3.h"); |
| 33 | |
| 34 | const KEEP_TEMPDIRS: bool = false; |
| 35 | |
| 36 | #[test] |
| 37 | fn test_help() -> Result<(), Box<dyn std::error::Error>> { |
| 38 | let mut cmd = Command::cargo_bin("autocxx-gen")?; |
| 39 | cmd.arg("-h").assert().success(); |
| 40 | Ok(()) |
| 41 | } |
| 42 | |
| 43 | enum RsGenMode { |
| 44 | Single, |
| 45 | Archive, |
| 46 | } |
| 47 | |
| 48 | fn base_test<F>( |
| 49 | tmp_dir: &TempDir, |
| 50 | rs_gen_mode: RsGenMode, |
| 51 | arg_modifier: F, |
| 52 | ) -> Result<(), Box<dyn std::error::Error>> |
| 53 | where |
| 54 | F: FnOnce(&mut Command), |
| 55 | { |
| 56 | let mut standard_files = HashMap::new(); |
| 57 | standard_files.insert("input.h", INPUT_H.as_bytes()); |
| 58 | standard_files.insert("main.rs", MAIN_RS.as_bytes()); |
| 59 | let result = base_test_ex( |
| 60 | tmp_dir, |
| 61 | rs_gen_mode, |
| 62 | arg_modifier, |
| 63 | standard_files, |
| 64 | vec!["main.rs"], |
| 65 | ); |
| 66 | assert_contentful(tmp_dir, "gen0.cc"); |
| 67 | result |
| 68 | } |
| 69 | |
| 70 | fn base_test_ex<F>( |
| 71 | tmp_dir: &TempDir, |
| 72 | rs_gen_mode: RsGenMode, |
| 73 | arg_modifier: F, |
| 74 | files_to_write: HashMap<&str, &[u8]>, |
| 75 | files_to_process: Vec<&str>, |
| 76 | ) -> Result<(), Box<dyn std::error::Error>> |
| 77 | where |
| 78 | F: FnOnce(&mut Command), |
| 79 | { |
| 80 | let demo_code_dir = tmp_dir.path().join("demo"); |
| 81 | std::fs::create_dir(&demo_code_dir).unwrap(); |
| 82 | for (filename, content) in files_to_write { |
| 83 | write_to_file(&demo_code_dir, filename, content); |
| 84 | } |
| 85 | let mut cmd = Command::cargo_bin("autocxx-gen")?; |
| 86 | arg_modifier(&mut cmd); |
| 87 | cmd.arg("--inc") |
| 88 | .arg(demo_code_dir.to_str().unwrap()) |
| 89 | .arg("--outdir") |
| 90 | .arg(tmp_dir.path().to_str().unwrap()) |
| 91 | .arg("--gen-cpp"); |
| 92 | cmd.arg(match rs_gen_mode { |
| 93 | RsGenMode::Single => "--gen-rs-include", |
| 94 | RsGenMode::Archive => "--gen-rs-archive", |
| 95 | }); |
| 96 | for file in files_to_process { |
| 97 | cmd.arg(demo_code_dir.join(file)); |
| 98 | } |
| 99 | let output = cmd.output(); |
| 100 | if let Ok(output) = output { |
| 101 | eprintln!("Cmd stdout: {:?}", std::str::from_utf8(&output.stdout)); |
| 102 | eprintln!("Cmd stderr: {:?}", std::str::from_utf8(&output.stderr)); |
| 103 | } |
| 104 | cmd.assert().success(); |
| 105 | Ok(()) |
| 106 | } |
| 107 | |
| 108 | #[test] |
| 109 | fn test_gen() -> Result<(), Box<dyn std::error::Error>> { |
| 110 | let tmp_dir = TempDir::new("example")?; |
| 111 | base_test(&tmp_dir, RsGenMode::Single, |_| {})?; |
| 112 | File::create(tmp_dir.path().join("cxx.h")) |
| 113 | .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?; |
| 114 | std::env::set_var("OUT_DIR", tmp_dir.path().to_str().unwrap()); |
| 115 | let r = build_from_folder( |
| 116 | tmp_dir.path(), |
| 117 | &tmp_dir.path().join("demo/main.rs"), |
| 118 | vec![tmp_dir.path().join("autocxx-ffi-default-gen.rs")], |
| 119 | &["gen0.cc"], |
| 120 | RsFindMode::AutocxxRs, |
| 121 | ); |
| 122 | if KEEP_TEMPDIRS { |
| 123 | println!("Tempdir: {:?}", tmp_dir.into_path().to_str()); |
| 124 | } |
| 125 | r.unwrap(); |
| 126 | Ok(()) |
| 127 | } |
| 128 | |
| 129 | #[test] |
| 130 | fn test_gen_archive() -> Result<(), Box<dyn std::error::Error>> { |
| 131 | let tmp_dir = TempDir::new("example")?; |
| 132 | base_test(&tmp_dir, RsGenMode::Archive, |_| {})?; |
| 133 | File::create(tmp_dir.path().join("cxx.h")) |
| 134 | .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?; |
| 135 | let r = build_from_folder( |
| 136 | tmp_dir.path(), |
| 137 | &tmp_dir.path().join("demo/main.rs"), |
| 138 | vec![tmp_dir.path().join("gen.rs.json")], |
| 139 | &["gen0.cc"], |
| 140 | RsFindMode::AutocxxRsArchive, |
| 141 | ); |
| 142 | if KEEP_TEMPDIRS { |
| 143 | println!("Tempdir: {:?}", tmp_dir.into_path().to_str()); |
| 144 | } |
| 145 | r.unwrap(); |
| 146 | Ok(()) |
| 147 | } |
| 148 | |
| 149 | #[test] |
| 150 | fn test_gen_multiple_in_archive() -> Result<(), Box<dyn std::error::Error>> { |
| 151 | let tmp_dir = TempDir::new("example")?; |
| 152 | |
| 153 | let mut files = HashMap::new(); |
| 154 | files.insert("input2.h", INPUT2_H.as_bytes()); |
| 155 | files.insert("input3.h", INPUT3_H.as_bytes()); |
| 156 | files.insert("main.rs", MAIN2_RS.as_bytes()); |
| 157 | files.insert("directive1.rs", DIRECTIVE1_RS.as_bytes()); |
| 158 | files.insert("directive2.rs", DIRECTIVE2_RS.as_bytes()); |
| 159 | base_test_ex( |
| 160 | &tmp_dir, |
| 161 | RsGenMode::Archive, |
| 162 | |cmd| { |
| 163 | cmd.arg("--generate-exact").arg("8"); |
| 164 | }, |
| 165 | files, |
| 166 | vec!["directive1.rs", "directive2.rs"], |
| 167 | )?; |
| 168 | File::create(tmp_dir.path().join("cxx.h")) |
| 169 | .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?; |
| 170 | // We've asked to create 8 C++ files, mostly blank. Build 'em all. |
| 171 | let cpp_files = (0..7).map(|id| format!("gen{}.cc", id)).collect_vec(); |
| 172 | let cpp_files = cpp_files.iter().map(|s| s.as_str()).collect_vec(); |
| 173 | let r = build_from_folder( |
| 174 | tmp_dir.path(), |
| 175 | &tmp_dir.path().join("demo/main.rs"), |
| 176 | vec![tmp_dir.path().join("gen.rs.json")], |
| 177 | &cpp_files, |
| 178 | RsFindMode::AutocxxRsArchive, |
| 179 | ); |
| 180 | if KEEP_TEMPDIRS { |
| 181 | println!("Tempdir: {:?}", tmp_dir.into_path().to_str()); |
| 182 | } |
| 183 | r.unwrap(); |
| 184 | Ok(()) |
| 185 | } |
| 186 | |
| 187 | #[test] |
| 188 | fn test_include_prefixes() -> Result<(), Box<dyn std::error::Error>> { |
| 189 | let tmp_dir = TempDir::new("example")?; |
| 190 | base_test(&tmp_dir, RsGenMode::Single, |cmd| { |
| 191 | cmd.arg("--cxx-h-path") |
| 192 | .arg("foo/") |
| 193 | .arg("--cxxgen-h-path") |
| 194 | .arg("bar/") |
| 195 | .arg("--generate-exact") |
| 196 | .arg("3"); |
| 197 | })?; |
| 198 | assert_contains(&tmp_dir, "autocxxgen0.h", "foo/cxx.h"); |
| 199 | // Currently we don't test cxxgen-h-path because we build the demo code |
| 200 | // which doesn't refer to generated cxx header code. |
| 201 | Ok(()) |
| 202 | } |
| 203 | |
| 204 | #[test] |
| 205 | fn test_gen_fixed_num() -> Result<(), Box<dyn std::error::Error>> { |
| 206 | let tmp_dir = TempDir::new("example")?; |
| 207 | let depfile = tmp_dir.path().join("test.d"); |
| 208 | base_test(&tmp_dir, RsGenMode::Single, |cmd| { |
| 209 | cmd.arg("--generate-exact") |
| 210 | .arg("2") |
| 211 | .arg("--depfile") |
| 212 | .arg(depfile); |
| 213 | })?; |
| 214 | assert_contentful(&tmp_dir, "gen0.cc"); |
| 215 | assert_contentful(&tmp_dir, "gen0.h"); |
| 216 | assert_not_contentful(&tmp_dir, "gen1.cc"); |
| 217 | assert_contentful(&tmp_dir, "autocxxgen0.h"); |
| 218 | assert_not_contentful(&tmp_dir, "gen1.h"); |
| 219 | assert_not_contentful(&tmp_dir, "autocxxgen1.h"); |
| 220 | assert_contentful(&tmp_dir, "autocxx-ffi-default-gen.rs"); |
| 221 | assert_contentful(&tmp_dir, "test.d"); |
| 222 | File::create(tmp_dir.path().join("cxx.h")) |
| 223 | .and_then(|mut cxx_h| cxx_h.write_all(autocxx_engine::HEADER.as_bytes()))?; |
| 224 | let r = build_from_folder( |
| 225 | tmp_dir.path(), |
| 226 | &tmp_dir.path().join("demo/main.rs"), |
| 227 | vec![tmp_dir.path().join("autocxx-ffi-default-gen.rs")], |
| 228 | &["gen0.cc"], |
| 229 | RsFindMode::AutocxxRs, |
| 230 | ); |
| 231 | if KEEP_TEMPDIRS { |
| 232 | println!("Tempdir: {:?}", tmp_dir.into_path().to_str()); |
| 233 | } |
| 234 | r.unwrap(); |
| 235 | Ok(()) |
| 236 | } |
| 237 | |
| 238 | #[test] |
| 239 | fn test_gen_preprocess() -> Result<(), Box<dyn std::error::Error>> { |
| 240 | let tmp_dir = TempDir::new("example")?; |
| 241 | let prepro_path = tmp_dir.path().join("preprocessed.h"); |
| 242 | base_test(&tmp_dir, RsGenMode::Single, |cmd| { |
| 243 | cmd.env("AUTOCXX_PREPROCESS", prepro_path.to_str().unwrap()); |
| 244 | })?; |
| 245 | assert_contentful(&tmp_dir, "preprocessed.h"); |
| 246 | // Check that a random thing from one of the headers in |
| 247 | // `ALL_KNOWN_SYSTEM_HEADERS` is included. |
| 248 | assert!(std::fs::read_to_string(prepro_path)?.contains("integer_sequence")); |
| 249 | Ok(()) |
| 250 | } |
| 251 | |
| 252 | #[test] |
| 253 | fn test_gen_repro() -> Result<(), Box<dyn std::error::Error>> { |
| 254 | let tmp_dir = TempDir::new("example")?; |
| 255 | let repro_path = tmp_dir.path().join("repro.json"); |
| 256 | base_test(&tmp_dir, RsGenMode::Single, |cmd| { |
| 257 | cmd.env("AUTOCXX_REPRO_CASE", repro_path.to_str().unwrap()); |
| 258 | })?; |
| 259 | assert_contentful(&tmp_dir, "repro.json"); |
| 260 | // Check that a random thing from one of the headers in |
| 261 | // `ALL_KNOWN_SYSTEM_HEADERS` is included. |
| 262 | assert!(std::fs::read_to_string(repro_path)?.contains("integer_sequence")); |
| 263 | Ok(()) |
| 264 | } |
| 265 | |
| 266 | fn write_to_file(dir: &Path, filename: &str, content: &[u8]) { |
| 267 | let path = dir.join(filename); |
| 268 | let mut f = File::create(&path).expect("Unable to create file"); |
| 269 | f.write_all(content).expect("Unable to write file"); |
| 270 | } |
| 271 | |
| 272 | fn assert_contentful(outdir: &TempDir, fname: &str) { |
| 273 | let p = outdir.path().join(fname); |
| 274 | if !p.exists() { |
| 275 | panic!("File {} didn't exist", p.to_string_lossy()); |
| 276 | } |
| 277 | assert!( |
| 278 | p.metadata().unwrap().len() > BLANK.len().try_into().unwrap(), |
| 279 | "File {} is empty", |
| 280 | fname |
| 281 | ); |
| 282 | } |
| 283 | |
| 284 | fn assert_not_contentful(outdir: &TempDir, fname: &str) { |
| 285 | let p = outdir.path().join(fname); |
| 286 | if !p.exists() { |
| 287 | panic!("File {} didn't exist", p.to_string_lossy()); |
| 288 | } |
| 289 | assert!( |
| 290 | p.metadata().unwrap().len() <= BLANK.len().try_into().unwrap(), |
| 291 | "File {} is not empty; it contains {}", |
| 292 | fname, |
| 293 | std::fs::read_to_string(&p).unwrap_or_default() |
| 294 | ); |
| 295 | } |
| 296 | |
| 297 | fn assert_contains(outdir: &TempDir, fname: &str, pattern: &str) { |
| 298 | let p = outdir.path().join(fname); |
| 299 | let content = std::fs::read_to_string(&p).expect(fname); |
| 300 | eprintln!("content = {}", content); |
| 301 | assert!(content.contains(pattern)); |
| 302 | } |