3f1b9901b5
Bug: ((data << 10) | (val & 0x3FF)) ^ 0x5412 被解析为 (data << 10) | ((val & 0x3FF) ^ 0x5412) 导致格式信息的 XOR mask 未覆盖前 5 个数据位,扫码器读到错误的 EC 级别和掩码编号
329 lines
10 KiB
Rust
329 lines
10 KiB
Rust
use qr_core::qr::{QrCode, QrConfig, VersionMode};
|
||
use qr_core::version::EcLevel;
|
||
|
||
/// 诊断: 验证格式信息编码 + 解码是否正确
|
||
#[test]
|
||
fn test_format_info_roundtrip() {
|
||
use qr_core::matrix::patterns::{encode_format_info, encode_version_info};
|
||
|
||
// M 级 (00) + mask 0 (000): data = 00000 = 0
|
||
let fmt0 = encode_format_info(0b00, 0);
|
||
// L 级 (01) + mask 3 (011): data = 01011 = 11
|
||
let fmt1 = encode_format_info(0b01, 3);
|
||
// H 级 (10) + mask 7 (111): data = 10111 = 23
|
||
let fmt2 = encode_format_info(0b10, 7);
|
||
|
||
// 不同输入应产生不同输出
|
||
assert_ne!(fmt0, fmt1);
|
||
assert_ne!(fmt1, fmt2);
|
||
assert_ne!(fmt0, fmt2);
|
||
|
||
// 应该在 15-bit 范围内
|
||
assert!(fmt0 < 0x8000);
|
||
assert!(fmt1 < 0x8000);
|
||
|
||
println!("格式信息 M+mask0: 0x{:04X}", fmt0);
|
||
println!("格式信息 L+mask3: 0x{:04X}", fmt1);
|
||
println!("格式信息 H+mask7: 0x{:04X}", fmt2);
|
||
|
||
// 版本信息编码
|
||
let v7 = encode_version_info(7);
|
||
println!("版本信息 v7: 0x{:06X}", v7);
|
||
// 前 6 bit 是版本号
|
||
assert_eq!((v7 >> 12) & 0x3F, 7);
|
||
}
|
||
|
||
/// 诊断: 打印 QR 码的格式信息比特
|
||
#[test]
|
||
fn test_dump_format_info() {
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let m = qr.modules();
|
||
|
||
println!("\n=== 格式信息 15 bit (bit14 到 bit0) ===");
|
||
println!("EC 级别: {:?}, 掩码: {}", qr.level, qr.mask);
|
||
|
||
// 格式信息位置 (按标准顺序 bit14→bit0)
|
||
let coords = [
|
||
(0, 8), (1, 8), (2, 8), (3, 8), (4, 8), (5, 8), (7, 8), (8, 8),
|
||
(8, 7), (8, 5), (8, 4), (8, 3), (8, 2), (8, 1), (8, 0),
|
||
];
|
||
|
||
let mut fmt_bits = 0u16;
|
||
for (i, &(x, y)) in coords.iter().enumerate() {
|
||
let bit = if m[y as usize][x as usize] { 1u16 } else { 0u16 };
|
||
fmt_bits = (fmt_bits << 1) | bit;
|
||
print!("{} ", if m[y as usize][x as usize] { '█' } else { '_' });
|
||
}
|
||
println!();
|
||
println!("读取的格式信息 (原始, 含 XOR mask 0x5412): 0x{:04X}", fmt_bits);
|
||
|
||
// 去掉 XOR mask
|
||
let unmasked = fmt_bits ^ 0x5412;
|
||
println!("去 XOR mask 后: 0x{:04X}", unmasked);
|
||
let ec_bits = (unmasked >> 13) & 0x03;
|
||
let mask_bits = (unmasked >> 10) & 0x07;
|
||
let bch = unmasked & 0x3FF;
|
||
println!("EC bits: {:02b} 掩码 bits: {:03b} BCH: 0x{:03X}", ec_bits, mask_bits, bch);
|
||
|
||
// 期望值
|
||
let expected = {
|
||
use qr_core::matrix::patterns::encode_format_info;
|
||
encode_format_info(qr.level.indicator_bits(), qr.mask)
|
||
};
|
||
println!("期望的格式信息: 0x{:04X}", expected);
|
||
println!("匹配: {}", if fmt_bits == expected { "✅" } else { "❌ 不匹配!" });
|
||
assert_eq!(fmt_bits, expected, "格式信息不匹配!");
|
||
}
|
||
#[test]
|
||
fn test_finder_patterns_present() {
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let size = qr.size() as usize;
|
||
let m = qr.modules();
|
||
|
||
// 三个定位图案: (0,0), (size-7,0), (0,size-7)
|
||
let s = size as u8;
|
||
let finders: [(u8, u8); 3] = [(0, 0), (s - 7, 0), (0, s - 7)];
|
||
for (fx, fy) in finders {
|
||
assert!(m[fy as usize][fx as usize], "定位({},{}): 左上角应为暗", fx, fy);
|
||
assert!(m[fy as usize][(fx + 6) as usize], "定位({},{}): 右上角应为暗", fx, fy);
|
||
assert!(m[(fy + 6) as usize][fx as usize], "定位({},{}): 左下角应为暗", fx, fy);
|
||
// 内部 3×3 是暗色
|
||
assert!(m[(fy + 2) as usize][(fx + 2) as usize], "定位({},{}): 中心3×3应为暗", fx, fy);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_timing_pattern_alternates() {
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let m = qr.modules();
|
||
let s = qr.size() as usize;
|
||
|
||
// 行6: 从列8到列s-8-1 应交替
|
||
for x in (8..s - 8).step_by(2) {
|
||
assert!(m[6][x], "时序 y=6 x={}: 偶数应为暗", x);
|
||
if x + 1 < s - 8 {
|
||
assert!(!m[6][x + 1], "时序 y=6 x={}: 奇数应为亮", x + 1);
|
||
}
|
||
}
|
||
// 列6: 从行8到行s-8-1 应交替
|
||
for y in (8..s - 8).step_by(2) {
|
||
assert!(m[y][6], "时序 x=6 y={}: 偶数应为暗", y);
|
||
if y + 1 < s - 8 {
|
||
assert!(!m[y + 1][6], "时序 x=6 y={}: 奇数应为亮", y + 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_dark_module_present() {
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let m = qr.modules();
|
||
let s = qr.size() as usize;
|
||
// 暗模块总是位于 (8, size-8)
|
||
assert!(m[s - 8][8], "暗模块 (8,{}) 缺失!", s - 8);
|
||
}
|
||
|
||
#[test]
|
||
fn test_format_info_written() {
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let m = qr.modules();
|
||
// 格式信息在定位图案旁,检查几个位置不是全亮
|
||
assert!(m[8][0] || m[8][1] || m[8][2] || !m[8][0],
|
||
"格式信息应已写入");
|
||
}
|
||
|
||
#[test]
|
||
fn test_svg_valid_structure() {
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let svg = qr.to_svg();
|
||
// SVG 应有正确的结构
|
||
assert!(svg.starts_with("<svg"), "SVG 应以 <svg 开头");
|
||
assert!(svg.contains("rect"), "SVG 应包含 rect 元素");
|
||
assert!(svg.contains("fill=\"black\""), "SVG 暗模块应是黑色");
|
||
assert!(svg.ends_with("</svg>\n") || svg.ends_with("</svg>"),
|
||
"SVG 应以 </svg> 结尾");
|
||
}
|
||
|
||
#[test]
|
||
fn test_quiet_zone_is_white() {
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let m = qr.modules();
|
||
let s = qr.size() as usize;
|
||
// 左上角分隔符区域 (7,0..7) 和 (0..7,7) 应为白色
|
||
for i in 0..8usize {
|
||
assert!(!m[7][i], "定位分隔符 (7,{}) 应为白色", i);
|
||
assert!(!m[i][7], "定位分隔符 ({},7) 应为白色", i);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_qr_structure_dump() {
|
||
// 打印矩阵到 stdout(用于调试)
|
||
let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||
let size = qr.size() as usize;
|
||
let m = qr.modules();
|
||
|
||
println!("\n=== QR Matrix {}x{} v{} mask{} ===", size, size, qr.version.0, qr.mask);
|
||
for y in 0..size {
|
||
for x in 0..size {
|
||
print!("{}", if m[y][x] { "##" } else { " " });
|
||
}
|
||
println!();
|
||
}
|
||
|
||
// 统计
|
||
let dark: usize = m.iter().flatten().filter(|&&x| x).count();
|
||
let total = size * size;
|
||
println!("\n暗/总: {}/{} = {:.1}%", dark, total, dark as f64 / total as f64 * 100.0);
|
||
println!("尺寸: {}×{}", size, size);
|
||
println!("版本: {} 掩码: {} 纠错: {:?}", qr.version.0, qr.mask, qr.level);
|
||
}
|
||
|
||
#[test]
|
||
fn test_encode_simple_text() {
|
||
let config = QrConfig::default();
|
||
let qr = QrCode::encode("HELLO WORLD", config).unwrap();
|
||
assert_eq!(qr.version.0, 1);
|
||
assert_eq!(qr.size(), 21);
|
||
}
|
||
|
||
#[test]
|
||
fn test_all_levels() {
|
||
for level in [EcLevel::L, EcLevel::M, EcLevel::Q, EcLevel::H] {
|
||
let config = QrConfig {
|
||
level,
|
||
..Default::default()
|
||
};
|
||
let qr = QrCode::encode("TEST", config).unwrap();
|
||
assert!(qr.size() >= 21);
|
||
assert!(qr.size() <= 177);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_chinese_text() {
|
||
let config = QrConfig::default();
|
||
let qr = QrCode::encode("你好世界", config).unwrap();
|
||
assert!(qr.size() >= 21);
|
||
}
|
||
|
||
#[test]
|
||
fn test_url_encoding() {
|
||
let config = QrConfig::default();
|
||
let qr = QrCode::encode("https://example.com/path?q=1", config).unwrap();
|
||
assert!(qr.size() >= 21);
|
||
}
|
||
|
||
#[test]
|
||
fn test_numeric_only_small_version() {
|
||
let mut config = QrConfig::default();
|
||
config.version = VersionMode::Fixed(1);
|
||
let qr = QrCode::encode("12345678901234567890", config).unwrap();
|
||
assert_eq!(qr.version.0, 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_fixed_version() {
|
||
let config = QrConfig {
|
||
version: VersionMode::Fixed(5),
|
||
..Default::default()
|
||
};
|
||
let qr = QrCode::encode("FIXED VERSION TEST", config).unwrap();
|
||
assert_eq!(qr.version.0, 5);
|
||
}
|
||
|
||
#[test]
|
||
fn test_empty_input_fails() {
|
||
let config = QrConfig::default();
|
||
let result = QrCode::encode("", config);
|
||
assert!(result.is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_svg_output() {
|
||
let qr = QrCode::encode("TEST", QrConfig::default()).unwrap();
|
||
let svg = qr.to_svg();
|
||
assert!(svg.contains("<svg"));
|
||
assert!(svg.contains("</svg>"));
|
||
assert!(svg.contains("fill=\"black\""));
|
||
}
|
||
|
||
#[test]
|
||
fn test_ascii_output() {
|
||
let qr = QrCode::encode("TEST", QrConfig::default()).unwrap();
|
||
let ascii = qr.to_ascii(false);
|
||
assert!(!ascii.is_empty());
|
||
assert!(ascii.contains('\n'));
|
||
// 应该有暗模块
|
||
assert!(ascii.contains("██"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_png_output() {
|
||
let qr = QrCode::encode("TEST", QrConfig::default()).unwrap();
|
||
let png = qr.to_png_bytes(4).unwrap();
|
||
assert!(!png.is_empty());
|
||
// PNG 文件应以 8 字节魔术签名开头
|
||
assert_eq!(&png[..8], &[137, 80, 78, 71, 13, 10, 26, 10]);
|
||
}
|
||
|
||
#[test]
|
||
fn test_modules_matrix() {
|
||
let qr = QrCode::encode("QR", QrConfig::default()).unwrap();
|
||
let modules = qr.modules();
|
||
assert_eq!(modules.len(), qr.size() as usize);
|
||
assert_eq!(modules[0].len(), qr.size() as usize);
|
||
// 至少有一些暗模块
|
||
let has_dark = modules.iter().any(|row| row.iter().any(|&m| m));
|
||
assert!(has_dark, "QR 码应该包含暗模块");
|
||
}
|
||
|
||
#[test]
|
||
fn test_margin_is_included_in_dimensions() {
|
||
let mut config = QrConfig::default();
|
||
config.margin = 2;
|
||
let qr = QrCode::encode("MARGIN TEST", config).unwrap();
|
||
|
||
// SVG 的总宽度应该包含 margin
|
||
let svg = qr.to_svg();
|
||
let matrix_size = qr.size() as u32;
|
||
let expected_total = matrix_size + 2 * 2u32;
|
||
assert!(svg.contains(&format!("width=\"{}\"", expected_total)));
|
||
|
||
// ASCII 输出行应该包含 margin 列
|
||
let ascii = qr.to_ascii(false);
|
||
let first_line = ascii.lines().next().unwrap();
|
||
let chars_per_module = 2; // ██ 是两个字符
|
||
assert_eq!(
|
||
first_line.chars().count(),
|
||
expected_total as usize * chars_per_module
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_long_text_auto_version() {
|
||
// 长文本应该自动选择更高的版本
|
||
let long_text = "A".repeat(200);
|
||
let qr = QrCode::encode(&long_text, QrConfig::default()).unwrap();
|
||
// 至少要 Version 2 以上
|
||
assert!(qr.version.0 >= 2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_special_chars() {
|
||
let config = QrConfig::default();
|
||
let qr = QrCode::encode("$%*+-./: SPACE", config).unwrap();
|
||
assert!(qr.size() >= 21);
|
||
}
|
||
|
||
#[test]
|
||
fn test_numeric_mode_efficiency() {
|
||
// 纯数字在 Version 1 L 级最多 41 位(约 7089 位数字)
|
||
let digits = "1".repeat(20);
|
||
let mut config = QrConfig::default();
|
||
config.version = VersionMode::Fixed(1);
|
||
config.level = EcLevel::L;
|
||
let qr = QrCode::encode(&digits, config).unwrap();
|
||
assert_eq!(qr.version.0, 1);
|
||
}
|