Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_net/tests/gzip_oracle.rs

6.5 KiB, 10 runs

created by r1870400018:19670, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1#![cfg(feature = "async")]
2//! The gzip encoder, checked against `gzip(1)` rather than against itself.
3//!
4//! A codec tested only against its own decoder agrees with itself, which is
5//! something a consistently wrong codec does just as well. The authority on
6//! whether a stream really is [RFC 1952] gzip is a program that did not come
7//! from this repository and that every client in the world already trusts, so
8//! both directions are put to it: what this crate writes, `gunzip` must read,
9//! and what `gzip` writes, this crate must read.
10//!
11//! [RFC 1952]: https://www.rfc-editor.org/rfc/rfc1952
12//!
13//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
14//! Anthropic Claude
15
16use oxedyne_fe2o3_core::prelude::*;
17use oxedyne_fe2o3_net::http::encoding;
18
19use std::{
20 fs,
21 path::PathBuf,
22 process::{
23 Command,
24 Stdio,
25 },
26};
27
28
29/// A working directory under the target tree, which Cargo hands to an
30/// integration test for exactly this. Never `/tmp`, which on these machines is
31/// memory.
32fn work_dir(name: &str) -> Outcome<PathBuf> {
33 let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(name);
34 res!(fs::create_dir_all(&dir));
35 Ok(dir)
36}
37
38/// Prose with the redundancy real markup has, so a ratio means something.
39fn sample() -> Vec<u8> {
40 let mut out = String::new();
41 for i in 0..400 {
42 out.push_str(&fmt!(
43 "<p class=\"line\" data-index=\"{}\">the quick brown fox jumps over \
44 the lazy dog</p>\n", i));
45 }
46 out.into_bytes()
47}
48
49/// Is the external tool available at all? A missing oracle must be loud, not a
50/// quietly passing test.
51fn require(tool: &str) -> Outcome<()> {
52 let found = match Command::new("which").arg(tool).stdout(Stdio::null()).status() {
53 Ok(st) => st.success(),
54 Err(_) => false,
55 };
56 if !found {
57 return Err(err!(
58 "The external oracle '{}' is not on PATH, so this test cannot check \
59 anything it claims to check.", tool;
60 Missing, Init));
61 }
62 Ok(())
63}
64
65/// What this crate writes, `gunzip` reads back byte for byte.
66#[test]
67fn gunzip_reads_what_this_crate_wrote() -> Outcome<()> {
68 res!(require("gunzip"));
69 let plain = sample();
70 let encoded = res!(encoding::gzip(&plain));
71
72 let dir = res!(work_dir("gzip_oracle_out"));
73 let path = dir.join("body.gz");
74 let _ = fs::remove_file(&path);
75 let _ = fs::remove_file(dir.join("body"));
76 res!(fs::write(&path, &encoded));
77
78 let out = res!(Command::new("gunzip").arg("-c").arg(&path).output());
79 if !out.status.success() {
80 return Err(err!(
81 "gunzip refused the stream this crate produced: {}",
82 String::from_utf8_lossy(&out.stderr);
83 Invalid, Data));
84 }
85 assert_eq!(out.stdout, plain,
86 "gunzip read back something other than what was encoded");
87 // And the saving is the point of the exercise.
88 assert!(encoded.len() * 4 < plain.len(),
89 "{} bytes of markup encoded to {}, which is not worth doing",
90 plain.len(), encoded.len());
91 Ok(())
92}
93
94/// What `gzip` writes, this crate reads back byte for byte.
95#[test]
96fn this_crate_reads_what_gzip_wrote() -> Outcome<()> {
97 res!(require("gzip"));
98 let plain = sample();
99
100 let dir = res!(work_dir("gzip_oracle_in"));
101 let path = dir.join("body");
102 let gz_path = dir.join("body.gz");
103 let _ = fs::remove_file(&path);
104 let _ = fs::remove_file(&gz_path);
105 res!(fs::write(&path, &plain));
106
107 let out = res!(Command::new("gzip").arg("-9").arg("-k").arg("-f").arg(&path).output());
108 if !out.status.success() {
109 return Err(err!("gzip failed: {}", String::from_utf8_lossy(&out.stderr);
110 IO, Data));
111 }
112 let encoded = res!(fs::read(&gz_path));
113 assert_eq!(res!(encoding::gunzip(&encoded)), plain,
114 "a member written by gzip(1) did not read back as what went into it");
115 Ok(())
116}
117
118/// The case that matters: the framing fields must describe the encoded body,
119/// and the encoded body must be a stream a client can actually read.
120#[test]
121fn an_encoded_response_carries_a_stream_gunzip_accepts() -> Outcome<()> {
122 use oxedyne_fe2o3_net::http::{
123 fields::{
124 HeaderFields,
125 HeaderFieldValue,
126 HeaderName,
127 },
128 msg::HttpMessage,
129 status::HttpStatus,
130 };
131
132 res!(require("gunzip"));
133 let plain = sample();
134
135 let mut req = HeaderFields::default();
136 req.insert(
137 HeaderName::AcceptEncoding,
138 res!(HeaderFieldValue::new(&HeaderName::AcceptEncoding, "gzip, deflate, br")),
139 None,
140 );
141 let coding = encoding::choose(
142 &req,
143 "text/html; charset=utf-8",
144 plain.len(),
145 encoding::MIN_BYTES_DEFAULT,
146 );
147 assert_eq!(coding, encoding::ContentCoding::Gzip);
148
149 let resp = HttpMessage::new_response(HttpStatus::OK)
150 .with_field(
151 HeaderName::ContentType,
152 res!(HeaderFieldValue::new(&HeaderName::ContentType, "text/html; charset=utf-8")))
153 .with_field(
154 HeaderName::ETag,
155 res!(HeaderFieldValue::new(&HeaderName::ETag, "\"68a1-3b\"")))
156 .with_body(plain.clone());
157
158 let rt = res!(tokio::runtime::Runtime::new());
159 let resp = res!(rt.block_on(encoding::encode(resp, coding)));
160
161 let held = res!(resp.header.fields.get_one(&HeaderName::ContentEncoding).ok_or_else(||
162 err!("The encoded response did not say so."; Missing)));
163 assert_eq!(fmt!("{}", held), "gzip");
164 let vary = res!(resp.header.fields.get_one(&HeaderName::Vary).ok_or_else(||
165 err!("The encoded response did not vary by the coding."; Missing)));
166 assert_eq!(fmt!("{}", vary).to_ascii_lowercase(), "accept-encoding");
167 let etag = res!(resp.header.fields.get_one(&HeaderName::ETag).ok_or_else(||
168 err!("The encoded response lost its validator."; Missing)));
169 assert_eq!(fmt!("{}", etag), "\"68a1-3b-gzip\"");
170 // `Content-Length` is taken from the body when the message is written, so
171 // this is the number that will go on the wire.
172 assert_eq!(resp.body_len(), resp.body.len());
173
174 let dir = res!(work_dir("gzip_oracle_resp"));
175 let path = dir.join("resp.gz");
176 let _ = fs::remove_file(&path);
177 res!(fs::write(&path, &resp.body));
178 let out = res!(Command::new("gunzip").arg("-c").arg(&path).output());
179 if !out.status.success() {
180 return Err(err!(
181 "gunzip refused the body of an encoded response: {}",
182 String::from_utf8_lossy(&out.stderr);
183 Invalid, Data));
184 }
185 assert_eq!(out.stdout, plain);
186 Ok(())
187}