|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | + |
| 3 | +use crate::cache::{ |
| 4 | + CacheCompleteness, CacheOmission, CandidateId, CompatibilityFingerprint, CompatibilityRecord, |
| 5 | + LanguageFeatureFingerprint, LoadedSnapshot, PackageFingerprint, ProjectInputDigest, |
| 6 | +}; |
| 7 | +use crate::config::{DEFAULT_MAX_OMISSIONS, ResourceLimits}; |
| 8 | +use crate::result::{CacheReasonCountOutput, IndexOutput, PlanDecisionCountsOutput}; |
| 9 | +use crate::{ |
| 10 | + CacheDisposition, Freshness, OutputEnvelope, OutputStatus, ProjectSelection, ResolverTier, |
| 11 | + SelectionProvenance, StatusOutput, |
| 12 | +}; |
| 13 | + |
| 14 | +use super::super::lifecycle::{CommandOutput, project_output}; |
| 15 | +use super::render_human; |
| 16 | + |
| 17 | +fn mixed_snapshot() -> LoadedSnapshot { |
| 18 | + let omissions = (0..265) |
| 19 | + .rev() |
| 20 | + .map(|index| CacheOmission { |
| 21 | + path: format!("src/file{index:04}.rs"), |
| 22 | + reason: match index { |
| 23 | + 0..250 => "file-count-limit", |
| 24 | + 250..260 => "file-too-large", |
| 25 | + _ => "read-error:other", |
| 26 | + } |
| 27 | + .into(), |
| 28 | + detail: "resource limit".into(), |
| 29 | + }) |
| 30 | + .collect::<Vec<_>>(); |
| 31 | + let language = LanguageFeatureFingerprint::current(); |
| 32 | + let package = PackageFingerprint::from_normalized(["test"]); |
| 33 | + let compatibility = CompatibilityFingerprint::new(language, package); |
| 34 | + let digest = ProjectInputDigest::from_inputs([] as [(&str, &str, [u8; 32]); 0]); |
| 35 | + LoadedSnapshot { |
| 36 | + candidate_id: CandidateId::new( |
| 37 | + compatibility, |
| 38 | + digest, |
| 39 | + CacheCompleteness::Partial, |
| 40 | + &omissions, |
| 41 | + ), |
| 42 | + compatibility: CompatibilityRecord { |
| 43 | + id: compatibility, |
| 44 | + language_fingerprint: language, |
| 45 | + package_fingerprint: package, |
| 46 | + created_at_ns: 1, |
| 47 | + }, |
| 48 | + input_digest: digest, |
| 49 | + completeness: CacheCompleteness::Partial, |
| 50 | + omissions, |
| 51 | + created_at_ns: 2, |
| 52 | + inventory_file_count: 3, |
| 53 | + inventory_total_bytes: 42, |
| 54 | + files: Vec::new(), |
| 55 | + tier_graphs: Vec::new(), |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +#[test] |
| 60 | +fn index_and_cached_status_count_reasons_outside_capped_entries() { |
| 61 | + let snapshot = mixed_snapshot(); |
| 62 | + let expected = vec![ |
| 63 | + CacheReasonCountOutput { |
| 64 | + reason: "file-count-limit".into(), |
| 65 | + count: 250, |
| 66 | + }, |
| 67 | + CacheReasonCountOutput { |
| 68 | + reason: "file-too-large".into(), |
| 69 | + count: 10, |
| 70 | + }, |
| 71 | + CacheReasonCountOutput { |
| 72 | + reason: "read-error:other".into(), |
| 73 | + count: 5, |
| 74 | + }, |
| 75 | + ]; |
| 76 | + let index = IndexOutput::from_loaded_snapshot( |
| 77 | + &snapshot, |
| 78 | + ResolverTier::Scope, |
| 79 | + 0, |
| 80 | + 0, |
| 81 | + 0, |
| 82 | + 1, |
| 83 | + PlanDecisionCountsOutput::default(), |
| 84 | + ); |
| 85 | + let selection = ProjectSelection { |
| 86 | + canonical_root: "/project".into(), |
| 87 | + canonical_source: None, |
| 88 | + provenance: SelectionProvenance::RootArgument, |
| 89 | + }; |
| 90 | + let project = project_output( |
| 91 | + &selection, |
| 92 | + &snapshot, |
| 93 | + ResolverTier::Scope, |
| 94 | + Freshness::Frozen, |
| 95 | + CacheDisposition::Hit, |
| 96 | + ); |
| 97 | + assert_eq!(index.omission_reasons, expected); |
| 98 | + assert_eq!(project.omission_reasons, expected); |
| 99 | + assert_eq!(index.omissions.len(), DEFAULT_MAX_OMISSIONS); |
| 100 | + assert_eq!(index.omissions, project.omissions); |
| 101 | + assert_eq!(index.omitted_files, 265); |
| 102 | + assert_eq!(project.omitted_files, 265); |
| 103 | + assert!(index.omissions_truncated); |
| 104 | + assert!(project.omissions_truncated); |
| 105 | + assert!( |
| 106 | + index |
| 107 | + .omissions |
| 108 | + .iter() |
| 109 | + .all(|entry| entry.reason != "read-error:other") |
| 110 | + ); |
| 111 | + assert_eq!(index.omissions[0].path, "src/file0000.rs"); |
| 112 | + assert_eq!(index.omissions[255].path, "src/file0255.rs"); |
| 113 | + |
| 114 | + let status = StatusOutput::from_loaded_snapshot(project, &snapshot, &ResourceLimits::default()); |
| 115 | + assert_eq!(status.cached_omissions, index.omissions); |
| 116 | + assert_eq!(status.project.omission_reasons, expected); |
| 117 | + assert_eq!(status.inventory.omitted_files, 265); |
| 118 | + let expected_json = serde_json::json!([ |
| 119 | + {"reason": "file-count-limit", "count": 250}, |
| 120 | + {"reason": "file-too-large", "count": 10}, |
| 121 | + {"reason": "read-error:other", "count": 5}, |
| 122 | + ]); |
| 123 | + let mut index_json = serde_json::to_value(&index).expect("index JSON"); |
| 124 | + let mut project_json = serde_json::to_value(&status.project).expect("project JSON"); |
| 125 | + assert_eq!(index_json["omission_reasons"], expected_json); |
| 126 | + assert_eq!(project_json["omissionReasons"], expected_json); |
| 127 | + index_json |
| 128 | + .as_object_mut() |
| 129 | + .expect("index object") |
| 130 | + .remove("omission_reasons"); |
| 131 | + project_json |
| 132 | + .as_object_mut() |
| 133 | + .expect("project object") |
| 134 | + .remove("omissionReasons"); |
| 135 | + assert!( |
| 136 | + serde_json::from_value::<IndexOutput>(index_json) |
| 137 | + .expect("older index contract") |
| 138 | + .omission_reasons |
| 139 | + .is_empty() |
| 140 | + ); |
| 141 | + assert!( |
| 142 | + serde_json::from_value::<crate::ProjectOutput>(project_json) |
| 143 | + .expect("older project contract") |
| 144 | + .omission_reasons |
| 145 | + .is_empty() |
| 146 | + ); |
| 147 | + |
| 148 | + for output in [ |
| 149 | + CommandOutput::Index(OutputEnvelope::new(OutputStatus::Partial, index)), |
| 150 | + CommandOutput::Status(OutputEnvelope::new(OutputStatus::Partial, status)), |
| 151 | + ] { |
| 152 | + let rendered = render_human(&output); |
| 153 | + assert!(rendered.contains("warning: omission entries truncated; listing 256 of 265\n")); |
| 154 | + let reasons = rendered |
| 155 | + .lines() |
| 156 | + .filter(|line| line.starts_with("omission reason=")) |
| 157 | + .collect::<Vec<_>>(); |
| 158 | + assert_eq!( |
| 159 | + reasons, |
| 160 | + [ |
| 161 | + "omission reason=file-count-limit count=250", |
| 162 | + "omission reason=file-too-large count=10", |
| 163 | + "omission reason=read-error:other count=5", |
| 164 | + ] |
| 165 | + ); |
| 166 | + assert_eq!( |
| 167 | + rendered |
| 168 | + .lines() |
| 169 | + .filter(|line| line.starts_with("omitted src/")) |
| 170 | + .count(), |
| 171 | + DEFAULT_MAX_OMISSIONS |
| 172 | + ); |
| 173 | + assert!(!rendered.contains("omitted src/file0260.rs")); |
| 174 | + } |
| 175 | +} |
0 commit comments