| 1 | use crate::cargo::{self, Metadata, PackageMetadata}; |
| 2 | use crate::dependencies::{self, Dependency, EditionOrInherit}; |
| 3 | use crate::directory::Directory; |
| 4 | use crate::env::Update; |
| 5 | use crate::error::{Error, Result}; |
| 6 | use crate::expand::{expand_globs, ExpandedTest}; |
| 7 | use crate::flock::Lock; |
| 8 | use crate::manifest::{Bin, Build, Config, Manifest, Name, Package, Workspace}; |
| 9 | use crate::message::{self, Fail, Warn}; |
| 10 | use crate::normalize::{self, Context, Variations}; |
| 11 | use crate::{features, rustflags, Expected, Runner, Test}; |
| 12 | use serde_derive::Deserialize; |
| 13 | use std::collections::{BTreeMap as Map, BTreeSet as Set}; |
| 14 | use std::env; |
| 15 | use std::ffi::{OsStr, OsString}; |
| 16 | use std::fs::{self, File}; |
| 17 | use std::mem; |
| 18 | use std::path::{Path, PathBuf}; |
| 19 | use std::str; |
| 20 | |
| 21 | #[derive(Debug)] |
| 22 | pub struct Project { |
| 23 | pub dir: Directory, |
| 24 | source_dir: Directory, |
| 25 | pub target_dir: Directory, |
| 26 | pub name: String, |
| 27 | update: Update, |
| 28 | pub has_pass: bool, |
| 29 | has_compile_fail: bool, |
| 30 | pub features: Option<Vec<String>>, |
| 31 | pub workspace: Directory, |
| 32 | pub path_dependencies: Vec<PathDependency>, |
| 33 | manifest: Manifest, |
| 34 | pub keep_going: bool, |
| 35 | } |
| 36 | |
| 37 | #[derive(Debug)] |
| 38 | pub struct PathDependency { |
| 39 | pub name: String, |
| 40 | pub normalized_path: Directory, |
| 41 | } |
| 42 | |
| 43 | struct Report { |
| 44 | failures: usize, |
| 45 | created_wip: usize, |
| 46 | } |
| 47 | |
| 48 | impl Runner { |
| 49 | pub fn run(&mut self) { |
| 50 | let mut tests = expand_globs(&self.tests); |
| 51 | filter(&mut tests); |
| 52 | |
| 53 | let (project, _lock) = (|| { |
| 54 | let mut project = self.prepare(&tests)?; |
| 55 | let lock = Lock::acquire(path!(project.dir / ".lock" ))?; |
| 56 | self.write(&mut project)?; |
| 57 | Ok((project, lock)) |
| 58 | })() |
| 59 | .unwrap_or_else(|err| { |
| 60 | message::prepare_fail(err); |
| 61 | panic!("tests failed" ); |
| 62 | }); |
| 63 | |
| 64 | print!(" \n\n" ); |
| 65 | |
| 66 | let len = tests.len(); |
| 67 | let mut report = Report { |
| 68 | failures: 0, |
| 69 | created_wip: 0, |
| 70 | }; |
| 71 | |
| 72 | if tests.is_empty() { |
| 73 | message::no_tests_enabled(); |
| 74 | } else if project.keep_going && !project.has_pass { |
| 75 | report = match self.run_all(&project, tests) { |
| 76 | Ok(failures) => failures, |
| 77 | Err(err) => { |
| 78 | message::test_fail(err); |
| 79 | Report { |
| 80 | failures: len, |
| 81 | created_wip: 0, |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | } else { |
| 86 | for test in tests { |
| 87 | match test.run(&project) { |
| 88 | Ok(Outcome::Passed) => {} |
| 89 | Ok(Outcome::CreatedWip) => report.created_wip += 1, |
| 90 | Err(err) => { |
| 91 | report.failures += 1; |
| 92 | message::test_fail(err); |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | print!(" \n\n" ); |
| 99 | |
| 100 | if report.failures > 0 && project.name != "trybuild-tests" { |
| 101 | panic!("{} of {} tests failed" , report.failures, len); |
| 102 | } |
| 103 | if report.created_wip > 0 && project.name != "trybuild-tests" { |
| 104 | panic!( |
| 105 | "successfully created new stderr files for {} test cases" , |
| 106 | report.created_wip, |
| 107 | ); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | fn prepare(&self, tests: &[ExpandedTest]) -> Result<Project> { |
| 112 | let Metadata { |
| 113 | target_directory: target_dir, |
| 114 | workspace_root: workspace, |
| 115 | packages, |
| 116 | } = cargo::metadata()?; |
| 117 | |
| 118 | let mut has_pass = false; |
| 119 | let mut has_compile_fail = false; |
| 120 | for e in tests { |
| 121 | match e.test.expected { |
| 122 | Expected::Pass => has_pass = true, |
| 123 | Expected::CompileFail => has_compile_fail = true, |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | let source_dir = cargo::manifest_dir()?; |
| 128 | let source_manifest = dependencies::get_manifest(&source_dir)?; |
| 129 | |
| 130 | let mut features = features::find(); |
| 131 | |
| 132 | let path_dependencies = source_manifest |
| 133 | .dependencies |
| 134 | .iter() |
| 135 | .filter_map(|(name, dep)| { |
| 136 | let path = dep.path.as_ref()?; |
| 137 | if packages.iter().any(|p| &p.name == name) { |
| 138 | // Skip path dependencies coming from the workspace itself |
| 139 | None |
| 140 | } else { |
| 141 | Some(PathDependency { |
| 142 | name: name.clone(), |
| 143 | normalized_path: path.canonicalize().ok()?, |
| 144 | }) |
| 145 | } |
| 146 | }) |
| 147 | .collect(); |
| 148 | |
| 149 | let crate_name = &source_manifest.package.name; |
| 150 | let project_dir = path!(target_dir / "tests" / "trybuild" / crate_name /); |
| 151 | fs::create_dir_all(&project_dir)?; |
| 152 | |
| 153 | let project_name = format!("{}-tests" , crate_name); |
| 154 | let manifest = self.make_manifest( |
| 155 | &workspace, |
| 156 | &project_name, |
| 157 | &source_dir, |
| 158 | &packages, |
| 159 | tests, |
| 160 | source_manifest, |
| 161 | )?; |
| 162 | |
| 163 | if let Some(enabled_features) = &mut features { |
| 164 | enabled_features.retain(|feature| manifest.features.contains_key(feature)); |
| 165 | } |
| 166 | |
| 167 | Ok(Project { |
| 168 | dir: project_dir, |
| 169 | source_dir, |
| 170 | target_dir, |
| 171 | name: project_name, |
| 172 | update: Update::env()?, |
| 173 | has_pass, |
| 174 | has_compile_fail, |
| 175 | features, |
| 176 | workspace, |
| 177 | path_dependencies, |
| 178 | manifest, |
| 179 | keep_going: false, |
| 180 | }) |
| 181 | } |
| 182 | |
| 183 | fn write(&self, project: &mut Project) -> Result<()> { |
| 184 | let manifest_toml = basic_toml::to_string(&project.manifest)?; |
| 185 | |
| 186 | let config = self.make_config(); |
| 187 | let config_toml = basic_toml::to_string(&config)?; |
| 188 | |
| 189 | fs::create_dir_all(path!(project.dir / ".cargo" ))?; |
| 190 | fs::write(path!(project.dir / ".cargo" / "config.toml" ), config_toml)?; |
| 191 | fs::write(path!(project.dir / "Cargo.toml" ), manifest_toml)?; |
| 192 | |
| 193 | let main_rs = b"\ |
| 194 | #![allow(unused_crate_dependencies, missing_docs)] \n\ |
| 195 | fn main() {} \n\ |
| 196 | " ; |
| 197 | fs::write(path!(project.dir / "main.rs" ), &main_rs[..])?; |
| 198 | |
| 199 | cargo::build_dependencies(project)?; |
| 200 | |
| 201 | Ok(()) |
| 202 | } |
| 203 | |
| 204 | fn make_manifest( |
| 205 | &self, |
| 206 | workspace: &Directory, |
| 207 | project_name: &str, |
| 208 | source_dir: &Directory, |
| 209 | packages: &[PackageMetadata], |
| 210 | tests: &[ExpandedTest], |
| 211 | source_manifest: dependencies::Manifest, |
| 212 | ) -> Result<Manifest> { |
| 213 | let crate_name = source_manifest.package.name; |
| 214 | let workspace_manifest = dependencies::get_workspace_manifest(workspace); |
| 215 | |
| 216 | let edition = match source_manifest.package.edition { |
| 217 | EditionOrInherit::Edition(edition) => edition, |
| 218 | EditionOrInherit::Inherit => workspace_manifest |
| 219 | .workspace |
| 220 | .package |
| 221 | .edition |
| 222 | .ok_or(Error::NoWorkspaceManifest)?, |
| 223 | }; |
| 224 | |
| 225 | let mut dependencies = Map::new(); |
| 226 | dependencies.extend(source_manifest.dependencies); |
| 227 | dependencies.extend(source_manifest.dev_dependencies); |
| 228 | |
| 229 | let cargo_toml_path = source_dir.join("Cargo.toml" ); |
| 230 | let mut has_lib_target = true; |
| 231 | for package_metadata in packages { |
| 232 | if package_metadata.manifest_path == cargo_toml_path { |
| 233 | has_lib_target = package_metadata |
| 234 | .targets |
| 235 | .iter() |
| 236 | .any(|target| target.crate_types != ["bin" ]); |
| 237 | } |
| 238 | } |
| 239 | if has_lib_target { |
| 240 | dependencies.insert( |
| 241 | crate_name.clone(), |
| 242 | Dependency { |
| 243 | version: None, |
| 244 | path: Some(source_dir.clone()), |
| 245 | optional: false, |
| 246 | default_features: false, |
| 247 | features: Vec::new(), |
| 248 | git: None, |
| 249 | branch: None, |
| 250 | tag: None, |
| 251 | rev: None, |
| 252 | workspace: false, |
| 253 | rest: Map::new(), |
| 254 | }, |
| 255 | ); |
| 256 | } |
| 257 | |
| 258 | let mut targets = source_manifest.target; |
| 259 | for target in targets.values_mut() { |
| 260 | let dev_dependencies = mem::take(&mut target.dev_dependencies); |
| 261 | target.dependencies.extend(dev_dependencies); |
| 262 | } |
| 263 | |
| 264 | let mut features = source_manifest.features; |
| 265 | for (feature, enables) in &mut features { |
| 266 | enables.retain(|en| { |
| 267 | let dep_name = match en.strip_prefix("dep:" ) { |
| 268 | Some(dep_name) => dep_name, |
| 269 | None => return false, |
| 270 | }; |
| 271 | if let Some(Dependency { optional: true, .. }) = dependencies.get(dep_name) { |
| 272 | return true; |
| 273 | } |
| 274 | for target in targets.values() { |
| 275 | if let Some(Dependency { optional: true, .. }) = |
| 276 | target.dependencies.get(dep_name) |
| 277 | { |
| 278 | return true; |
| 279 | } |
| 280 | } |
| 281 | false |
| 282 | }); |
| 283 | if has_lib_target { |
| 284 | enables.insert(0, format!("{}/{}" , crate_name, feature)); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | let mut manifest = Manifest { |
| 289 | package: Package { |
| 290 | name: project_name.to_owned(), |
| 291 | version: "0.0.0" .to_owned(), |
| 292 | edition, |
| 293 | resolver: source_manifest.package.resolver, |
| 294 | publish: false, |
| 295 | }, |
| 296 | features, |
| 297 | dependencies, |
| 298 | target: targets, |
| 299 | bins: Vec::new(), |
| 300 | workspace: Some(Workspace { |
| 301 | dependencies: workspace_manifest.workspace.dependencies, |
| 302 | }), |
| 303 | // Within a workspace, only the [patch] and [replace] sections in |
| 304 | // the workspace root's Cargo.toml are applied by Cargo. |
| 305 | patch: workspace_manifest.patch, |
| 306 | replace: workspace_manifest.replace, |
| 307 | }; |
| 308 | |
| 309 | manifest.bins.push(Bin { |
| 310 | name: Name(project_name.to_owned()), |
| 311 | path: Path::new("main.rs" ).to_owned(), |
| 312 | }); |
| 313 | |
| 314 | for expanded in tests { |
| 315 | if expanded.error.is_none() { |
| 316 | manifest.bins.push(Bin { |
| 317 | name: expanded.name.clone(), |
| 318 | path: source_dir.join(&expanded.test.path), |
| 319 | }); |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | Ok(manifest) |
| 324 | } |
| 325 | |
| 326 | fn make_config(&self) -> Config { |
| 327 | Config { |
| 328 | build: Build { |
| 329 | rustflags: rustflags::make_vec(), |
| 330 | }, |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | fn run_all(&self, project: &Project, tests: Vec<ExpandedTest>) -> Result<Report> { |
| 335 | let mut report = Report { |
| 336 | failures: 0, |
| 337 | created_wip: 0, |
| 338 | }; |
| 339 | |
| 340 | let mut path_map = Map::new(); |
| 341 | for t in &tests { |
| 342 | let src_path = project.source_dir.join(&t.test.path); |
| 343 | path_map.insert(src_path, (&t.name, &t.test)); |
| 344 | } |
| 345 | |
| 346 | let output = cargo::build_all_tests(project)?; |
| 347 | let parsed = parse_cargo_json(project, &output.stdout, &path_map); |
| 348 | let fallback = Stderr::default(); |
| 349 | |
| 350 | for mut t in tests { |
| 351 | let show_expected = false; |
| 352 | message::begin_test(&t.test, show_expected); |
| 353 | |
| 354 | if t.error.is_none() { |
| 355 | t.error = check_exists(&t.test.path).err(); |
| 356 | } |
| 357 | |
| 358 | if t.error.is_none() { |
| 359 | let src_path = project.source_dir.join(&t.test.path); |
| 360 | let this_test = parsed.stderrs.get(&src_path).unwrap_or(&fallback); |
| 361 | match t.test.check(project, &t.name, this_test, "" ) { |
| 362 | Ok(Outcome::Passed) => {} |
| 363 | Ok(Outcome::CreatedWip) => report.created_wip += 1, |
| 364 | Err(error) => t.error = Some(error), |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | if let Some(err) = t.error { |
| 369 | report.failures += 1; |
| 370 | message::test_fail(err); |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | Ok(report) |
| 375 | } |
| 376 | } |
| 377 | |
| 378 | enum Outcome { |
| 379 | Passed, |
| 380 | CreatedWip, |
| 381 | } |
| 382 | |
| 383 | impl Test { |
| 384 | fn run(&self, project: &Project, name: &Name) -> Result<Outcome> { |
| 385 | let show_expected = project.has_pass && project.has_compile_fail; |
| 386 | message::begin_test(self, show_expected); |
| 387 | check_exists(&self.path)?; |
| 388 | |
| 389 | let mut path_map = Map::new(); |
| 390 | let src_path = project.source_dir.join(&self.path); |
| 391 | path_map.insert(src_path.clone(), (name, self)); |
| 392 | |
| 393 | let output = cargo::build_test(project, name)?; |
| 394 | let parsed = parse_cargo_json(project, &output.stdout, &path_map); |
| 395 | let fallback = Stderr::default(); |
| 396 | let this_test = parsed.stderrs.get(&src_path).unwrap_or(&fallback); |
| 397 | self.check(project, name, this_test, &parsed.stdout) |
| 398 | } |
| 399 | |
| 400 | fn check( |
| 401 | &self, |
| 402 | project: &Project, |
| 403 | name: &Name, |
| 404 | result: &Stderr, |
| 405 | build_stdout: &str, |
| 406 | ) -> Result<Outcome> { |
| 407 | let check = match self.expected { |
| 408 | Expected::Pass => Test::check_pass, |
| 409 | Expected::CompileFail => Test::check_compile_fail, |
| 410 | }; |
| 411 | |
| 412 | check( |
| 413 | self, |
| 414 | project, |
| 415 | name, |
| 416 | result.success, |
| 417 | build_stdout, |
| 418 | &result.stderr, |
| 419 | ) |
| 420 | } |
| 421 | |
| 422 | fn check_pass( |
| 423 | &self, |
| 424 | project: &Project, |
| 425 | name: &Name, |
| 426 | success: bool, |
| 427 | build_stdout: &str, |
| 428 | variations: &Variations, |
| 429 | ) -> Result<Outcome> { |
| 430 | let preferred = variations.preferred(); |
| 431 | if !success { |
| 432 | message::failed_to_build(preferred); |
| 433 | return Err(Error::CargoFail); |
| 434 | } |
| 435 | |
| 436 | let mut output = cargo::run_test(project, name)?; |
| 437 | output.stdout.splice(..0, build_stdout.bytes()); |
| 438 | message::output(preferred, &output); |
| 439 | if output.status.success() { |
| 440 | Ok(Outcome::Passed) |
| 441 | } else { |
| 442 | Err(Error::RunFailed) |
| 443 | } |
| 444 | } |
| 445 | |
| 446 | fn check_compile_fail( |
| 447 | &self, |
| 448 | project: &Project, |
| 449 | _name: &Name, |
| 450 | success: bool, |
| 451 | build_stdout: &str, |
| 452 | variations: &Variations, |
| 453 | ) -> Result<Outcome> { |
| 454 | let preferred = variations.preferred(); |
| 455 | |
| 456 | if success { |
| 457 | message::should_not_have_compiled(); |
| 458 | message::fail_output(Fail, build_stdout); |
| 459 | message::warnings(preferred); |
| 460 | return Err(Error::ShouldNotHaveCompiled); |
| 461 | } |
| 462 | |
| 463 | let stderr_path = self.path.with_extension("stderr" ); |
| 464 | |
| 465 | if !stderr_path.exists() { |
| 466 | let outcome = match project.update { |
| 467 | Update::Wip => { |
| 468 | let wip_dir = Path::new("wip" ); |
| 469 | fs::create_dir_all(wip_dir)?; |
| 470 | let gitignore_path = wip_dir.join(".gitignore" ); |
| 471 | fs::write(gitignore_path, "* \n" )?; |
| 472 | let stderr_name = stderr_path |
| 473 | .file_name() |
| 474 | .unwrap_or_else(|| OsStr::new("test.stderr" )); |
| 475 | let wip_path = wip_dir.join(stderr_name); |
| 476 | message::write_stderr_wip(&wip_path, &stderr_path, preferred); |
| 477 | fs::write(wip_path, preferred).map_err(Error::WriteStderr)?; |
| 478 | Outcome::CreatedWip |
| 479 | } |
| 480 | Update::Overwrite => { |
| 481 | message::overwrite_stderr(&stderr_path, preferred); |
| 482 | fs::write(stderr_path, preferred).map_err(Error::WriteStderr)?; |
| 483 | Outcome::Passed |
| 484 | } |
| 485 | }; |
| 486 | message::fail_output(Warn, build_stdout); |
| 487 | return Ok(outcome); |
| 488 | } |
| 489 | |
| 490 | let expected = fs::read_to_string(&stderr_path) |
| 491 | .map_err(Error::ReadStderr)? |
| 492 | .replace(" \r\n" , " \n" ); |
| 493 | |
| 494 | if variations.any(|stderr| expected == stderr) { |
| 495 | message::ok(); |
| 496 | return Ok(Outcome::Passed); |
| 497 | } |
| 498 | |
| 499 | match project.update { |
| 500 | Update::Wip => { |
| 501 | message::mismatch(&expected, preferred); |
| 502 | Err(Error::Mismatch) |
| 503 | } |
| 504 | Update::Overwrite => { |
| 505 | message::overwrite_stderr(&stderr_path, preferred); |
| 506 | fs::write(stderr_path, preferred).map_err(Error::WriteStderr)?; |
| 507 | Ok(Outcome::Passed) |
| 508 | } |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | fn check_exists(path: &Path) -> Result<()> { |
| 514 | if path.exists() { |
| 515 | return Ok(()); |
| 516 | } |
| 517 | match File::open(path) { |
| 518 | Ok(_) => Ok(()), |
| 519 | Err(err) => Err(Error::Open(path.to_owned(), err)), |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | impl ExpandedTest { |
| 524 | fn run(self, project: &Project) -> Result<Outcome> { |
| 525 | match self.error { |
| 526 | None => self.test.run(project, &self.name), |
| 527 | Some(error) => { |
| 528 | let show_expected = false; |
| 529 | message::begin_test(&self.test, show_expected); |
| 530 | Err(error) |
| 531 | } |
| 532 | } |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | // Filter which test cases are run by trybuild. |
| 537 | // |
| 538 | // $ cargo test -- ui trybuild=tuple_structs.rs |
| 539 | // |
| 540 | // The first argument after `--` must be the trybuild test name i.e. the name of |
| 541 | // the function that has the #[test] attribute and calls trybuild. That's to get |
| 542 | // Cargo to run the test at all. The next argument starting with `trybuild=` |
| 543 | // provides a filename filter. Only test cases whose filename contains the |
| 544 | // filter string will be run. |
| 545 | #[allow (clippy::needless_collect)] // false positive https://github.com/rust-lang/rust-clippy/issues/5991 |
| 546 | fn filter(tests: &mut Vec<ExpandedTest>) { |
| 547 | let filters = env::args_os() |
| 548 | .flat_map(OsString::into_string) |
| 549 | .filter_map(|mut arg| { |
| 550 | const PREFIX: &str = "trybuild=" ; |
| 551 | if arg.starts_with(PREFIX) && arg != PREFIX { |
| 552 | Some(arg.split_off(PREFIX.len())) |
| 553 | } else { |
| 554 | None |
| 555 | } |
| 556 | }) |
| 557 | .collect::<Vec<String>>(); |
| 558 | |
| 559 | if filters.is_empty() { |
| 560 | return; |
| 561 | } |
| 562 | |
| 563 | tests.retain(|t| { |
| 564 | filters |
| 565 | .iter() |
| 566 | .any(|f| t.test.path.to_string_lossy().contains(f)) |
| 567 | }); |
| 568 | } |
| 569 | |
| 570 | #[derive(Deserialize)] |
| 571 | struct CargoMessage { |
| 572 | #[allow (dead_code)] |
| 573 | reason: Reason, |
| 574 | target: RustcTarget, |
| 575 | message: RustcMessage, |
| 576 | } |
| 577 | |
| 578 | #[derive(Deserialize)] |
| 579 | enum Reason { |
| 580 | #[serde(rename = "compiler-message" )] |
| 581 | CompilerMessage, |
| 582 | } |
| 583 | |
| 584 | #[derive(Deserialize)] |
| 585 | struct RustcTarget { |
| 586 | src_path: PathBuf, |
| 587 | } |
| 588 | |
| 589 | #[derive(Deserialize)] |
| 590 | struct RustcMessage { |
| 591 | rendered: String, |
| 592 | level: String, |
| 593 | } |
| 594 | |
| 595 | struct ParsedOutputs { |
| 596 | stdout: String, |
| 597 | stderrs: Map<PathBuf, Stderr>, |
| 598 | } |
| 599 | |
| 600 | struct Stderr { |
| 601 | success: bool, |
| 602 | stderr: Variations, |
| 603 | } |
| 604 | |
| 605 | impl Default for Stderr { |
| 606 | fn default() -> Self { |
| 607 | Stderr { |
| 608 | success: true, |
| 609 | stderr: Variations::default(), |
| 610 | } |
| 611 | } |
| 612 | } |
| 613 | |
| 614 | fn parse_cargo_json( |
| 615 | project: &Project, |
| 616 | stdout: &[u8], |
| 617 | path_map: &Map<PathBuf, (&Name, &Test)>, |
| 618 | ) -> ParsedOutputs { |
| 619 | let mut map = Map::new(); |
| 620 | let mut nonmessage_stdout = String::new(); |
| 621 | let mut remaining = &*String::from_utf8_lossy(stdout); |
| 622 | let mut seen = Set::new(); |
| 623 | while !remaining.is_empty() { |
| 624 | let begin = match remaining.find("{ \"reason \":" ) { |
| 625 | Some(begin) => begin, |
| 626 | None => break, |
| 627 | }; |
| 628 | let (nonmessage, rest) = remaining.split_at(begin); |
| 629 | nonmessage_stdout.push_str(nonmessage); |
| 630 | let len = match rest.find(' \n' ) { |
| 631 | Some(end) => end + 1, |
| 632 | None => rest.len(), |
| 633 | }; |
| 634 | let (message, rest) = rest.split_at(len); |
| 635 | remaining = rest; |
| 636 | if !seen.insert(message) { |
| 637 | // Discard duplicate messages. This might no longer be necessary |
| 638 | // after https://github.com/rust-lang/rust/issues/106571 is fixed. |
| 639 | // Normally rustc would filter duplicates itself and I think this is |
| 640 | // a short-lived bug. |
| 641 | continue; |
| 642 | } |
| 643 | if let Ok(de) = serde_json::from_str::<CargoMessage>(message) { |
| 644 | if de.message.level != "failure-note" { |
| 645 | let (name, test) = match path_map.get(&de.target.src_path) { |
| 646 | Some(test) => test, |
| 647 | None => continue, |
| 648 | }; |
| 649 | let entry = map |
| 650 | .entry(de.target.src_path) |
| 651 | .or_insert_with(Stderr::default); |
| 652 | if de.message.level == "error" { |
| 653 | entry.success = false; |
| 654 | } |
| 655 | let normalized = normalize::diagnostics( |
| 656 | &de.message.rendered, |
| 657 | Context { |
| 658 | krate: &name.0, |
| 659 | source_dir: &project.source_dir, |
| 660 | workspace: &project.workspace, |
| 661 | input_file: &test.path, |
| 662 | target_dir: &project.target_dir, |
| 663 | path_dependencies: &project.path_dependencies, |
| 664 | }, |
| 665 | ); |
| 666 | entry.stderr.concat(&normalized); |
| 667 | } |
| 668 | } |
| 669 | } |
| 670 | nonmessage_stdout.push_str(remaining); |
| 671 | ParsedOutputs { |
| 672 | stdout: nonmessage_stdout, |
| 673 | stderrs: map, |
| 674 | } |
| 675 | } |
| 676 | |