1use asupersync::bytes::{Bytes, BytesMut};
9use asupersync::http::h2::{
10 Connection, Header, HpackEncoder, Settings,
11 frame::{Frame, HeadersFrame, PriorityFrame, PrioritySpec, SettingsFrame},
12};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::fmt;
16
17const H2_REFERENCE_UNAVAILABLE: &str =
18 "h2 reference comparison unavailable in standalone frame harness";
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub enum PriorityTestVerdict {
23 Pass,
24 Fail,
25 ExpectedFailure, Skipped,
27}
28
29impl fmt::Display for PriorityTestVerdict {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::Pass => write!(f, "PASS"),
33 Self::Fail => write!(f, "FAIL"),
34 Self::ExpectedFailure => write!(f, "XFAIL"),
35 Self::Skipped => write!(f, "SKIP"),
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub enum RequirementLevel {
43 Must, Should, May, }
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct StreamPriorityState {
51 pub stream_id: u32,
52 pub exclusive: bool,
53 pub dependency: u32,
54 pub weight: u8,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct SerializablePrioritySpec {
60 pub exclusive: bool,
61 pub dependency: u32,
62 pub weight: u8,
63}
64
65impl From<PrioritySpec> for SerializablePrioritySpec {
66 fn from(spec: PrioritySpec) -> Self {
67 Self {
68 exclusive: spec.exclusive,
69 dependency: spec.dependency,
70 weight: spec.weight,
71 }
72 }
73}
74
75impl From<SerializablePrioritySpec> for PrioritySpec {
76 fn from(spec: SerializablePrioritySpec) -> Self {
77 Self {
78 exclusive: spec.exclusive,
79 dependency: spec.dependency,
80 weight: spec.weight,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SerializablePriorityFrame {
88 pub stream_id: u32,
89 pub priority: SerializablePrioritySpec,
90}
91
92impl From<PriorityFrame> for SerializablePriorityFrame {
93 fn from(frame: PriorityFrame) -> Self {
94 Self {
95 stream_id: frame.stream_id,
96 priority: frame.priority.into(),
97 }
98 }
99}
100
101impl From<SerializablePriorityFrame> for PriorityFrame {
102 fn from(frame: SerializablePriorityFrame) -> Self {
103 Self {
104 stream_id: frame.stream_id,
105 priority: frame.priority.into(),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct PriorityConformanceCase {
113 pub id: String,
114 pub description: String,
115 pub requirement_level: RequirementLevel,
116 pub priority_sequence: Vec<SerializablePriorityFrame>,
118 pub expected_priority_graph: Vec<StreamPriorityState>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct PriorityConformanceResult {
125 pub case_id: String,
126 pub verdict: PriorityTestVerdict,
127 pub error: Option<String>,
128 pub asupersync_priorities: Option<Vec<StreamPriorityState>>,
130 pub h2_priorities: Option<Vec<StreamPriorityState>>,
132 pub differences: Vec<String>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct PriorityComplianceSummary {
139 pub total_cases: usize,
140 pub passed: usize,
141 pub failed: usize,
142 pub expected_failures: usize,
143 pub skipped: usize,
144 pub compliance_score: f64, }
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct PriorityComplianceReport {
150 pub test_run_id: String,
151 pub timestamp: chrono::DateTime<chrono::Utc>,
152 pub total_cases: usize,
153 pub results: Vec<PriorityConformanceResult>,
154 pub summary: PriorityComplianceSummary,
155}
156
157impl PriorityComplianceReport {
158 fn new(results: Vec<PriorityConformanceResult>) -> Self {
160 let total_cases = results.len();
161 let passed = results
162 .iter()
163 .filter(|r| r.verdict == PriorityTestVerdict::Pass)
164 .count();
165 let failed = results
166 .iter()
167 .filter(|r| r.verdict == PriorityTestVerdict::Fail)
168 .count();
169 let expected_failures = results
170 .iter()
171 .filter(|r| r.verdict == PriorityTestVerdict::ExpectedFailure)
172 .count();
173 let skipped = results
174 .iter()
175 .filter(|r| r.verdict == PriorityTestVerdict::Skipped)
176 .count();
177
178 let compliance_score = if total_cases > 0 {
179 (passed + expected_failures) as f64 / total_cases as f64
180 } else {
181 1.0
182 };
183
184 let summary = PriorityComplianceSummary {
185 total_cases,
186 passed,
187 failed,
188 expected_failures,
189 skipped,
190 compliance_score,
191 };
192
193 Self {
194 test_run_id: uuid::Uuid::new_v4().to_string(),
195 timestamp: chrono::Utc::now(),
196 total_cases,
197 results,
198 summary,
199 }
200 }
201}
202
203#[derive(Debug)]
205pub struct PriorityConformanceTester {
206 pub test_cases: Vec<PriorityConformanceCase>,
207}
208
209impl PriorityConformanceTester {
210 pub fn new() -> Self {
212 Self {
213 test_cases: create_priority_test_cases(),
214 }
215 }
216
217 pub async fn run_all_tests(&self) -> PriorityComplianceReport {
219 let mut results = Vec::new();
220
221 for case in &self.test_cases {
222 let result = self.run_single_test(case).await;
223 results.push(result);
224 }
225
226 PriorityComplianceReport::new(results)
227 }
228
229 async fn run_single_test(&self, case: &PriorityConformanceCase) -> PriorityConformanceResult {
231 let asupersync_result = self.test_asupersync_priorities(case).await;
233
234 let h2_result = self.test_h2_priorities(case).await;
238
239 let (verdict, error, differences) = match (&asupersync_result, &h2_result) {
241 (Ok(asupersync_priorities), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => {
242 let differences = self
243 .compare_priority_states(asupersync_priorities, &case.expected_priority_graph);
244 if differences.is_empty() {
245 (
246 PriorityTestVerdict::ExpectedFailure,
247 Some(format!(
248 "{h2_err}; live asupersync matched the RFC-expected state but vendor parity remains unexercised"
249 )),
250 differences,
251 )
252 } else {
253 (
254 PriorityTestVerdict::Fail,
255 Some(format!(
256 "Live asupersync PRIORITY state differed from expected RFC behavior while {h2_err}"
257 )),
258 differences,
259 )
260 }
261 }
262 (Err(asupersync_err), Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
263 PriorityTestVerdict::Fail,
264 Some(format!(
265 "Live asupersync PRIORITY processing failed while {h2_err}: {asupersync_err}"
266 )),
267 vec![format!("asupersync_error: {asupersync_err}")],
268 ),
269 (Ok(asupersync_priorities), Ok(h2_priorities)) => {
270 let differences =
271 self.compare_priority_states(asupersync_priorities, h2_priorities);
272 if differences.is_empty() {
273 (PriorityTestVerdict::Pass, None, differences)
274 } else {
275 (
276 PriorityTestVerdict::Fail,
277 Some(format!(
278 "Priority state differences: {}",
279 differences.join(", ")
280 )),
281 differences,
282 )
283 }
284 }
285 (_, Err(h2_err)) if h2_err == H2_REFERENCE_UNAVAILABLE => (
286 PriorityTestVerdict::Skipped,
287 Some(h2_err.clone()),
288 Vec::new(),
289 ),
290 (Err(asupersync_err), Err(h2_err)) => {
291 if asupersync_err == h2_err {
293 (PriorityTestVerdict::Pass, None, Vec::new())
294 } else {
295 (
296 PriorityTestVerdict::Fail,
297 Some(format!(
298 "Different error behaviors: asupersync={}, h2={}",
299 asupersync_err, h2_err
300 )),
301 vec![format!(
302 "Error divergence: {} vs {}",
303 asupersync_err, h2_err
304 )],
305 )
306 }
307 }
308 (Ok(_), Err(h2_err)) => (
309 PriorityTestVerdict::Fail,
310 Some(format!("asupersync succeeded, h2 failed: {}", h2_err)),
311 vec!["Implementation success divergence".to_string()],
312 ),
313 (Err(asupersync_err), Ok(_)) => (
314 PriorityTestVerdict::Fail,
315 Some(format!(
316 "asupersync failed, h2 succeeded: {}",
317 asupersync_err
318 )),
319 vec!["Implementation success divergence".to_string()],
320 ),
321 };
322
323 PriorityConformanceResult {
324 case_id: case.id.clone(),
325 verdict,
326 error,
327 asupersync_priorities: asupersync_result.as_ref().ok().cloned(),
328 h2_priorities: h2_result.as_ref().ok().cloned(),
329 differences,
330 }
331 }
332
333 async fn test_asupersync_priorities(
335 &self,
336 case: &PriorityConformanceCase,
337 ) -> Result<Vec<StreamPriorityState>, String> {
338 let settings = Settings::default();
339 let mut connection = Connection::server(settings);
340 accept_peer_settings(&mut connection)?;
341 let stream_ids = priority_stream_ids(&case.priority_sequence);
342
343 for stream_id in &stream_ids {
344 initialize_remote_stream(&mut connection, *stream_id)?;
345 }
346
347 for serializable_frame in &case.priority_sequence {
349 let priority_frame: PriorityFrame = serializable_frame.clone().into();
350 if let Err(e) = process_live_priority_frame(&mut connection, &priority_frame) {
351 return Err(format!("Failed to process PRIORITY frame: {}", e));
352 }
353 }
354
355 let priority_states = extract_asupersync_priority_states(&connection, &stream_ids);
357 Ok(priority_states)
358 }
359
360 async fn test_h2_priorities(
362 &self,
363 _case: &PriorityConformanceCase,
364 ) -> Result<Vec<StreamPriorityState>, String> {
365 Err(H2_REFERENCE_UNAVAILABLE.to_string())
366 }
367
368 fn compare_priority_states(
370 &self,
371 asupersync: &[StreamPriorityState],
372 h2: &[StreamPriorityState],
373 ) -> Vec<String> {
374 let mut differences = Vec::new();
375
376 let asupersync_map: HashMap<u32, &StreamPriorityState> =
378 asupersync.iter().map(|s| (s.stream_id, s)).collect();
379 let h2_map: HashMap<u32, &StreamPriorityState> =
380 h2.iter().map(|s| (s.stream_id, s)).collect();
381
382 for &stream_id in asupersync_map.keys() {
384 if !h2_map.contains_key(&stream_id) {
385 differences.push(format!(
386 "Stream {} present in asupersync but not h2",
387 stream_id
388 ));
389 }
390 }
391
392 for &stream_id in h2_map.keys() {
394 if !asupersync_map.contains_key(&stream_id) {
395 differences.push(format!(
396 "Stream {} present in h2 but not asupersync",
397 stream_id
398 ));
399 }
400 }
401
402 for (&stream_id, &asupersync_state) in &asupersync_map {
404 if let Some(&h2_state) = h2_map.get(&stream_id) {
405 if asupersync_state.exclusive != h2_state.exclusive {
406 differences.push(format!(
407 "Stream {} exclusive flag differs: asupersync={}, h2={}",
408 stream_id, asupersync_state.exclusive, h2_state.exclusive
409 ));
410 }
411 if asupersync_state.dependency != h2_state.dependency {
412 differences.push(format!(
413 "Stream {} dependency differs: asupersync={}, h2={}",
414 stream_id, asupersync_state.dependency, h2_state.dependency
415 ));
416 }
417 if asupersync_state.weight != h2_state.weight {
418 differences.push(format!(
419 "Stream {} weight differs: asupersync={}, h2={}",
420 stream_id, asupersync_state.weight, h2_state.weight
421 ));
422 }
423 }
424 }
425
426 differences
427 }
428
429 pub fn generate_markdown_report(&self, report: &PriorityComplianceReport) -> String {
431 let mut output = String::new();
432 output.push_str("# HTTP/2 PRIORITY Frame Conformance Report\n\n");
433
434 output.push_str(&format!("**Test Run ID:** {}\n", report.test_run_id));
435 output.push_str(&format!("**Timestamp:** {}\n", report.timestamp));
436 output.push_str(&format!("**Total Test Cases:** {}\n\n", report.total_cases));
437
438 output.push_str("## Summary\n\n");
439 output.push_str(&format!("- **Passed:** {}\n", report.summary.passed));
440 output.push_str(&format!("- **Failed:** {}\n", report.summary.failed));
441 output.push_str(&format!(
442 "- **Expected Failures:** {}\n",
443 report.summary.expected_failures
444 ));
445 output.push_str(&format!("- **Skipped:** {}\n", report.summary.skipped));
446 output.push_str(&format!(
447 "- **Compliance Score:** {:.1}%\n\n",
448 report.summary.compliance_score * 100.0
449 ));
450
451 if report.summary.failed > 0 {
452 output.push_str("## Failures\n\n");
453 for result in &report.results {
454 if result.verdict == PriorityTestVerdict::Fail {
455 output.push_str(&format!("### {}\n", result.case_id));
456 if let Some(error) = &result.error {
457 output.push_str(&format!("**Error:** {}\n", error));
458 }
459 if !result.differences.is_empty() {
460 output.push_str("**Differences:**\n");
461 for diff in &result.differences {
462 output.push_str(&format!("- {}\n", diff));
463 }
464 }
465 output.push('\n');
466 }
467 }
468 }
469
470 output.push_str("## All Results\n\n");
471 output.push_str("| Case ID | Verdict | Description |\n");
472 output.push_str("|---------|---------|-------------|\n");
473 for result in &report.results {
474 output.push_str(&format!(
475 "| {} | {} | Case {} |\n",
476 result.case_id, result.verdict, result.case_id
477 ));
478 }
479
480 output
481 }
482}
483
484impl Default for PriorityConformanceTester {
485 fn default() -> Self {
486 Self::new()
487 }
488}
489
490fn accept_peer_settings(connection: &mut Connection) -> Result<(), String> {
491 let received = connection
492 .process_frame(Frame::Settings(SettingsFrame::new(vec![])))
493 .map_err(|err| err.to_string())?;
494 if received.is_some() {
495 return Err("SETTINGS handshake produced an application frame".to_string());
496 }
497
498 match connection.next_frame() {
499 Some(Frame::Settings(settings)) if settings.ack => Ok(()),
500 other => Err(format!(
501 "SETTINGS handshake should queue exactly one ACK, got {other:?}"
502 )),
503 }
504}
505
506fn request_header_block(stream_id: u32) -> Bytes {
507 let headers = [
508 Header::new(":method", "GET"),
509 Header::new(":scheme", "https"),
510 Header::new(":authority", "example.test"),
511 Header::new(":path", format!("/priority/{stream_id}")),
512 ];
513 let mut encoder = HpackEncoder::new();
514 let mut block = BytesMut::new();
515 encoder.encode(&headers, &mut block);
516 block.freeze()
517}
518
519fn initialize_remote_stream(connection: &mut Connection, stream_id: u32) -> Result<(), String> {
520 let headers = HeadersFrame::new(stream_id, request_header_block(stream_id), false, true);
521 connection
522 .process_frame(Frame::Headers(headers))
523 .map_err(|err| err.to_string())?;
524 Ok(())
525}
526
527fn priority_stream_ids(sequence: &[SerializablePriorityFrame]) -> Vec<u32> {
528 let mut stream_ids = Vec::new();
529 for frame in sequence {
530 if frame.stream_id != 0 && !stream_ids.contains(&frame.stream_id) {
531 stream_ids.push(frame.stream_id);
532 }
533 }
534 stream_ids
535}
536
537fn process_live_priority_frame(
539 connection: &mut Connection,
540 priority_frame: &PriorityFrame,
541) -> Result<(), String> {
542 let received = connection
543 .process_frame(Frame::Priority(*priority_frame))
544 .map_err(|err| err.to_string())?;
545 if received.is_some() {
546 return Err(format!(
547 "PRIORITY produced unexpected application frame: {received:?}"
548 ));
549 }
550 Ok(())
551}
552
553fn extract_asupersync_priority_states(
555 connection: &Connection,
556 stream_ids: &[u32],
557) -> Vec<StreamPriorityState> {
558 stream_ids
559 .iter()
560 .filter_map(|&stream_id| {
561 let priority = connection.stream(stream_id)?.priority();
562 Some(StreamPriorityState {
563 stream_id,
564 exclusive: priority.exclusive,
565 dependency: priority.dependency,
566 weight: priority.weight,
567 })
568 })
569 .collect()
570}
571
572fn create_priority_test_cases() -> Vec<PriorityConformanceCase> {
574 vec![
575 PriorityConformanceCase {
577 id: "priority-001".to_string(),
578 description: "Basic PRIORITY frame sets stream priority correctly".to_string(),
579 requirement_level: RequirementLevel::Must,
580 priority_sequence: vec![SerializablePriorityFrame {
581 stream_id: 1,
582 priority: SerializablePrioritySpec {
583 exclusive: false,
584 dependency: 0,
585 weight: 16,
586 },
587 }],
588 expected_priority_graph: vec![StreamPriorityState {
589 stream_id: 1,
590 exclusive: false,
591 dependency: 0,
592 weight: 16,
593 }],
594 },
595 PriorityConformanceCase {
597 id: "priority-002".to_string(),
598 description: "PRIORITY frame with exclusive dependency flag".to_string(),
599 requirement_level: RequirementLevel::Must,
600 priority_sequence: vec![SerializablePriorityFrame {
601 stream_id: 3,
602 priority: SerializablePrioritySpec {
603 exclusive: true,
604 dependency: 1,
605 weight: 32,
606 },
607 }],
608 expected_priority_graph: vec![StreamPriorityState {
609 stream_id: 3,
610 exclusive: true,
611 dependency: 1,
612 weight: 32,
613 }],
614 },
615 PriorityConformanceCase {
617 id: "priority-003".to_string(),
618 description: "Multiple streams with dependency chain".to_string(),
619 requirement_level: RequirementLevel::Must,
620 priority_sequence: vec![
621 SerializablePriorityFrame {
622 stream_id: 1,
623 priority: SerializablePrioritySpec {
624 exclusive: false,
625 dependency: 0,
626 weight: 16,
627 },
628 },
629 SerializablePriorityFrame {
630 stream_id: 3,
631 priority: SerializablePrioritySpec {
632 exclusive: false,
633 dependency: 1,
634 weight: 8,
635 },
636 },
637 SerializablePriorityFrame {
638 stream_id: 5,
639 priority: SerializablePrioritySpec {
640 exclusive: false,
641 dependency: 3,
642 weight: 4,
643 },
644 },
645 ],
646 expected_priority_graph: vec![
647 StreamPriorityState {
648 stream_id: 1,
649 exclusive: false,
650 dependency: 0,
651 weight: 16,
652 },
653 StreamPriorityState {
654 stream_id: 3,
655 exclusive: false,
656 dependency: 1,
657 weight: 8,
658 },
659 StreamPriorityState {
660 stream_id: 5,
661 exclusive: false,
662 dependency: 3,
663 weight: 4,
664 },
665 ],
666 },
667 PriorityConformanceCase {
669 id: "priority-004".to_string(),
670 description: "PRIORITY weight at minimum and maximum values".to_string(),
671 requirement_level: RequirementLevel::Must,
672 priority_sequence: vec![
673 SerializablePriorityFrame {
674 stream_id: 1,
675 priority: SerializablePrioritySpec {
676 exclusive: false,
677 dependency: 0,
678 weight: 1, },
680 },
681 SerializablePriorityFrame {
682 stream_id: 3,
683 priority: SerializablePrioritySpec {
684 exclusive: false,
685 dependency: 0,
686 weight: 255, },
688 },
689 ],
690 expected_priority_graph: vec![
691 StreamPriorityState {
692 stream_id: 1,
693 exclusive: false,
694 dependency: 0,
695 weight: 1,
696 },
697 StreamPriorityState {
698 stream_id: 3,
699 exclusive: false,
700 dependency: 0,
701 weight: 255,
702 },
703 ],
704 },
705 PriorityConformanceCase {
707 id: "priority-005".to_string(),
708 description: "Update priority on existing stream".to_string(),
709 requirement_level: RequirementLevel::Must,
710 priority_sequence: vec![
711 SerializablePriorityFrame {
713 stream_id: 1,
714 priority: SerializablePrioritySpec {
715 exclusive: false,
716 dependency: 0,
717 weight: 16,
718 },
719 },
720 SerializablePriorityFrame {
722 stream_id: 1,
723 priority: SerializablePrioritySpec {
724 exclusive: true,
725 dependency: 3,
726 weight: 64,
727 },
728 },
729 ],
730 expected_priority_graph: vec![StreamPriorityState {
731 stream_id: 1,
732 exclusive: true,
733 dependency: 3,
734 weight: 64,
735 }],
736 },
737 PriorityConformanceCase {
739 id: "priority-006".to_string(),
740 description: "Multiple streams depending on the same parent".to_string(),
741 requirement_level: RequirementLevel::Should,
742 priority_sequence: vec![
743 SerializablePriorityFrame {
744 stream_id: 3,
745 priority: SerializablePrioritySpec {
746 exclusive: false,
747 dependency: 1,
748 weight: 32,
749 },
750 },
751 SerializablePriorityFrame {
752 stream_id: 5,
753 priority: SerializablePrioritySpec {
754 exclusive: false,
755 dependency: 1,
756 weight: 16,
757 },
758 },
759 SerializablePriorityFrame {
760 stream_id: 7,
761 priority: SerializablePrioritySpec {
762 exclusive: false,
763 dependency: 1,
764 weight: 8,
765 },
766 },
767 ],
768 expected_priority_graph: vec![
769 StreamPriorityState {
770 stream_id: 3,
771 exclusive: false,
772 dependency: 1,
773 weight: 32,
774 },
775 StreamPriorityState {
776 stream_id: 5,
777 exclusive: false,
778 dependency: 1,
779 weight: 16,
780 },
781 StreamPriorityState {
782 stream_id: 7,
783 exclusive: false,
784 dependency: 1,
785 weight: 8,
786 },
787 ],
788 },
789 PriorityConformanceCase {
791 id: "priority-007".to_string(),
792 description: "Handle circular dependency in priority graph".to_string(),
793 requirement_level: RequirementLevel::Must,
794 priority_sequence: vec![
795 SerializablePriorityFrame {
796 stream_id: 1,
797 priority: SerializablePrioritySpec {
798 exclusive: false,
799 dependency: 3,
800 weight: 16,
801 },
802 },
803 SerializablePriorityFrame {
804 stream_id: 3,
805 priority: SerializablePrioritySpec {
806 exclusive: false,
807 dependency: 1, weight: 16,
809 },
810 },
811 ],
812 expected_priority_graph: vec![
813 StreamPriorityState {
814 stream_id: 1,
815 exclusive: false,
816 dependency: 3,
819 weight: 16,
820 },
821 StreamPriorityState {
822 stream_id: 3,
823 exclusive: false,
824 dependency: 1,
825 weight: 16,
826 },
827 ],
828 },
829 ]
830}
831
832#[cfg(test)]
833mod tests {
834 use super::*;
835 use asupersync::http::h2::frame::{FrameHeader, FrameType, parse_frame};
836
837 #[tokio::test]
838 async fn h2_reference_unavailable_fails_closed_after_live_priority_assertions() {
839 let tester = PriorityConformanceTester::new();
840 let report = tester.run_all_tests().await;
841
842 assert_eq!(report.total_cases, 7);
843 assert_eq!(report.summary.passed, 0);
844 assert_eq!(report.summary.failed, 0);
845 assert_eq!(report.summary.expected_failures, 7);
846 assert_eq!(report.summary.skipped, 0);
847 assert!(
848 report
849 .results
850 .iter()
851 .all(|result| result.verdict == PriorityTestVerdict::ExpectedFailure),
852 "unwired h2 reference must not produce PASS verdicts"
853 );
854 assert!(
855 report.results.iter().all(|result| result
856 .error
857 .as_deref()
858 .is_some_and(|error| error.contains(H2_REFERENCE_UNAVAILABLE)
859 && error.contains("vendor parity remains unexercised"))),
860 "each xfail must name the missing vendor reference"
861 );
862 assert!(
863 report
864 .results
865 .iter()
866 .all(|result| result.h2_priorities.is_none()),
867 "h2 reference is intentionally not wired for this harness"
868 );
869 assert!(
870 report
871 .results
872 .iter()
873 .all(|result| result.asupersync_priorities.is_some()),
874 "every case must exercise the live asupersync connection"
875 );
876 }
877
878 #[test]
879 fn priority_frame_updates_live_stream_priority() {
880 let mut connection = Connection::server(Settings::default());
881 accept_peer_settings(&mut connection).expect("SETTINGS handshake");
882 initialize_remote_stream(&mut connection, 1).expect("open stream");
883
884 let priority = PriorityFrame {
885 stream_id: 1,
886 priority: PrioritySpec {
887 exclusive: true,
888 dependency: 3,
889 weight: 64,
890 },
891 };
892 process_live_priority_frame(&mut connection, &priority).expect("PRIORITY should process");
893
894 let observed = connection.stream(1).unwrap().priority();
895 assert!(observed.exclusive);
896 assert_eq!(observed.dependency, 3);
897 assert_eq!(observed.weight, 64);
898 }
899
900 #[test]
901 fn priority_on_idle_stream_is_accepted_without_creating_stream() {
902 let mut connection = Connection::server(Settings::default());
903 accept_peer_settings(&mut connection).expect("SETTINGS handshake");
904
905 let priority = PriorityFrame {
906 stream_id: 9,
907 priority: PrioritySpec {
908 exclusive: false,
909 dependency: 0,
910 weight: 32,
911 },
912 };
913 process_live_priority_frame(&mut connection, &priority)
914 .expect("PRIORITY on an idle stream is legal at the frame seam");
915 assert!(
916 connection.stream(9).is_none(),
917 "current asupersync records priority only for existing streams"
918 );
919 }
920
921 #[test]
922 fn priority_cycle_metadata_is_reported_without_tree_rewrite() {
923 let mut connection = Connection::server(Settings::default());
924 accept_peer_settings(&mut connection).expect("SETTINGS handshake");
925 initialize_remote_stream(&mut connection, 1).expect("stream 1");
926 initialize_remote_stream(&mut connection, 3).expect("stream 3");
927
928 let first = PriorityFrame {
929 stream_id: 1,
930 priority: PrioritySpec {
931 exclusive: false,
932 dependency: 3,
933 weight: 16,
934 },
935 };
936 let second = PriorityFrame {
937 stream_id: 3,
938 priority: PrioritySpec {
939 exclusive: false,
940 dependency: 1,
941 weight: 16,
942 },
943 };
944 process_live_priority_frame(&mut connection, &first).expect("first priority");
945 process_live_priority_frame(&mut connection, &second).expect("second priority");
946
947 assert_eq!(connection.stream(1).unwrap().priority().dependency, 3);
948 assert_eq!(connection.stream(3).unwrap().priority().dependency, 1);
949 }
950
951 #[test]
952 fn priority_stream_zero_is_rejected_by_parser() {
953 let header = FrameHeader {
954 length: 5,
955 frame_type: FrameType::Priority as u8,
956 flags: 0,
957 stream_id: 0,
958 };
959 let payload = Bytes::from_static(&[0, 0, 0, 0, 16]);
960 assert!(
961 parse_frame(&header, payload).is_err(),
962 "PRIORITY on stream 0 must be rejected at the parser seam"
963 );
964 }
965}