use alloc::vec::Vec;
use super::*;
use crate::error::{ParseError, ParseErrorKind, PayloadKind};
use crate::source::SourceLineNumber;
use crate::test_support::{
TestFailure, TestResult, ensure_eq, ensure_matches, expect_error_position, source_column,
source_line_number,
};
fn validated_runtime_byte(byte: u8) -> Result<RuntimeByte, TestFailure> {
RuntimeByte::validate_input_boundary(byte, 0).map_err(TestFailure::from)?;
Ok(RuntimeByte::from_validated_input(byte))
}
fn parse_payload_error(
input: &[CompactByte],
line_number: SourceLineNumber,
payload_kind: PayloadKind,
) -> Result<ParseError, TestFailure> {
match Payload::parse(input, line_number, payload_kind) {
Ok(_) => Err(TestFailure::message("invalid payload bytes were accepted")),
Err(error) => Ok(error),
}
}
#[test]
fn payload_rejects_every_reserved_syntax_byte_even_if_payload_parser_is_called_directly()
-> TestResult {
for reserved in [b'=', b'#', b'(', b')'] {
let compact = [CompactByte::new(reserved, source_column(1)?)];
let error =
parse_payload_error(&compact, source_line_number(1)?, PayloadKind::RightSideData)?;
expect_error_position(&error, 1, 1)?;
ensure_matches(
matches!(
error.kind(),
ParseErrorKind::ReservedSyntaxInPayload { byte, .. }
if byte.get() == reserved
),
"expected concrete reserved syntax byte",
)?;
ensure_matches(
matches!(
error.kind(),
ParseErrorKind::ReservedSyntaxInPayload {
payload_kind: PayloadKind::RightSideData,
..
}
),
"expected reserved syntax payload error",
)?;
}
Ok(())
}
#[test]
fn payload_validates_compact_bytes_at_the_domain_boundary() -> TestResult {
let non_ascii = [CompactByte::new(0xff, source_column(1)?)];
let non_graphic = [CompactByte::new(b' ', source_column(2)?)];
let error = parse_payload_error(
&non_ascii,
source_line_number(1)?,
PayloadKind::RightSideData,
)?;
ensure_matches(
matches!(error.kind(), ParseErrorKind::NonAsciiInCode { .. }),
"expected non-ASCII parse error",
)?;
let error = parse_payload_error(
&non_graphic,
source_line_number(1)?,
PayloadKind::RightSideData,
)?;
expect_error_position(&error, 1, 2)?;
ensure_matches(
matches!(error.kind(), ParseErrorKind::NonPrintableAsciiInCode { .. }),
"expected non-printable parse error",
)?;
Ok(())
}
#[test]
fn payload_exposes_validated_bytes_without_leaking_the_internal_domain_type() -> TestResult {
let compact = [
CompactByte::new(b'a', source_column(1)?),
CompactByte::new(b'b', source_column(2)?),
];
let payload = Payload::parse(&compact, source_line_number(1)?, PayloadKind::LeftSideData)
.map_err(TestFailure::from)?;
ensure_eq!(payload.bytes().collect::<Vec<_>>(), b"ab".to_vec())?;
let PayloadNeedle::NonEmpty(needle) = payload.needle() else {
return Err(TestFailure::message("expected non-empty payload needle"));
};
ensure_eq!(needle.first_byte().get(), b'a')?;
Ok(())
}
#[test]
fn runtime_input_classifies_program_constructible_and_opaque_ascii_separately() -> TestResult {
let parsed = validated_runtime_byte(b'a')?;
ensure_matches(
matches!(parsed, RuntimeByte::ProgramConstructible(byte) if byte.get() == b'a'),
"expected program-constructible input byte",
)?;
ensure_eq!(parsed.materialize(), b'a')?;
for byte in [0x00, b' ', b'=', b'#', b'(', b')'] {
let parsed = validated_runtime_byte(byte)?;
ensure_eq!(parsed.materialize(), byte)?;
ensure_matches(
matches!(parsed, RuntimeByte::Opaque(_)),
"expected opaque input byte",
)?;
}
Ok(())
}
#[test]
fn runtime_input_validation_has_no_reconstruction_invariant() -> TestResult {
let parsed = validated_runtime_byte(b'a')?;
ensure_matches(
matches!(parsed, RuntimeByte::ProgramConstructible(byte) if byte.get() == b'a'),
"expected ASCII runtime input to validate normally",
)
}