#![cfg(test)]
use alloc::string::{FromUtf8Error, String};
use crate::error::{
AllocationError, OwnedRuleAttemptStepError, OwnedRunStepError, ParseError, ParseErrorLocation,
RuleAttemptStepError, RunAdmissionError, RunError, RunFinishError, RunStartError, RunStepError,
RuntimeInputError, TraceSnapshotRunError,
};
use crate::input::{RunSeed, RuntimeInput, RuntimeInputSource};
use crate::limits::{
DEFAULT_MAX_INPUT_LEN, DEFAULT_MAX_RETURN_LEN, DEFAULT_MAX_STATE_LEN, DEFAULT_MAX_STEPS,
DEFAULT_PARSE_LIMITS, ExecutionLimits, ReturnByteLimit, RuntimeInputByteLimit,
RuntimeInputLimits, RuntimeStateByteLimit, StepLimit,
};
use crate::program::Program;
use crate::source::{ProgramSource, SourceColumn, SourceLineNumber, SourcePosition};
pub(crate) enum TestFailure {
Message(String),
Parse(ParseError),
Input(RuntimeInputError),
Admission(RunAdmissionError),
Run(RunError),
RunStart(RunStartError),
RunFinish(RunFinishError),
RunStep(RunStepError),
OwnedRunStep(OwnedRunStepError),
RuleAttemptStep(RuleAttemptStepError),
OwnedRuleAttemptStep(OwnedRuleAttemptStepError),
TraceSnapshot(String),
Utf8(FromUtf8Error),
Allocation(AllocationError),
}
impl TestFailure {
pub(crate) fn message(message: impl Into<String>) -> Self {
Self::Message(message.into())
}
}
impl core::fmt::Debug for TestFailure {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Message(message) => formatter.debug_tuple("Message").field(message).finish(),
Self::Parse(error) => formatter.debug_tuple("Parse").field(error).finish(),
Self::Input(error) => formatter.debug_tuple("Input").field(error).finish(),
Self::Admission(error) => formatter.debug_tuple("Admission").field(error).finish(),
Self::Run(error) => formatter.debug_tuple("Run").field(error).finish(),
Self::RunStart(error) => formatter.debug_tuple("RunStart").field(error).finish(),
Self::RunFinish(error) => formatter.debug_tuple("RunFinish").field(error).finish(),
Self::RunStep(error) => formatter.debug_tuple("RunStep").field(error).finish(),
Self::OwnedRunStep(error) => {
formatter.debug_tuple("OwnedRunStep").field(error).finish()
}
Self::RuleAttemptStep(error) => formatter
.debug_tuple("RuleAttemptStep")
.field(error)
.finish(),
Self::OwnedRuleAttemptStep(error) => formatter
.debug_tuple("OwnedRuleAttemptStep")
.field(error)
.finish(),
Self::TraceSnapshot(error) => {
formatter.debug_tuple("TraceSnapshot").field(error).finish()
}
Self::Utf8(error) => formatter.debug_tuple("Utf8").field(error).finish(),
Self::Allocation(error) => formatter.debug_tuple("Allocation").field(error).finish(),
}
}
}
impl From<ParseError> for TestFailure {
fn from(value: ParseError) -> Self {
Self::Parse(value)
}
}
impl From<RunError> for TestFailure {
fn from(value: RunError) -> Self {
Self::Run(value)
}
}
impl From<RunStartError> for TestFailure {
fn from(value: RunStartError) -> Self {
Self::RunStart(value)
}
}
impl From<RunFinishError> for TestFailure {
fn from(value: RunFinishError) -> Self {
Self::RunFinish(value)
}
}
impl From<RunStepError> for TestFailure {
fn from(value: RunStepError) -> Self {
Self::RunStep(value)
}
}
impl From<OwnedRunStepError> for TestFailure {
fn from(value: OwnedRunStepError) -> Self {
Self::OwnedRunStep(value)
}
}
impl From<RuleAttemptStepError> for TestFailure {
fn from(value: RuleAttemptStepError) -> Self {
Self::RuleAttemptStep(value)
}
}
impl From<OwnedRuleAttemptStepError> for TestFailure {
fn from(value: OwnedRuleAttemptStepError) -> Self {
Self::OwnedRuleAttemptStep(value)
}
}
impl<E> From<TraceSnapshotRunError<E>> for TestFailure
where
E: core::fmt::Debug,
{
fn from(value: TraceSnapshotRunError<E>) -> Self {
Self::TraceSnapshot(std::format!("{value:?}"))
}
}
impl From<FromUtf8Error> for TestFailure {
fn from(value: FromUtf8Error) -> Self {
Self::Utf8(value)
}
}
impl From<AllocationError> for TestFailure {
fn from(value: AllocationError) -> Self {
Self::Allocation(value)
}
}
impl From<RuntimeInputError> for TestFailure {
fn from(value: RuntimeInputError) -> Self {
Self::Input(value)
}
}
impl From<RunAdmissionError> for TestFailure {
fn from(value: RunAdmissionError) -> Self {
Self::Admission(value)
}
}
pub(crate) type TestResult = Result<(), TestFailure>;
#[derive(Clone, Copy)]
pub(crate) struct TestRunPolicy {
input: RuntimeInputLimits,
execution: ExecutionLimits,
}
impl TestRunPolicy {
#[must_use]
pub(crate) const fn new(
max_input_len: RuntimeInputByteLimit,
max_steps: StepLimit,
max_state_len: RuntimeStateByteLimit,
max_return_len: ReturnByteLimit,
) -> Self {
Self {
input: RuntimeInputLimits::new(max_input_len),
execution: ExecutionLimits::new(max_steps, max_state_len, max_return_len),
}
}
#[must_use]
pub(crate) const fn default() -> Self {
Self::new(
DEFAULT_MAX_INPUT_LEN,
DEFAULT_MAX_STEPS,
DEFAULT_MAX_STATE_LEN,
DEFAULT_MAX_RETURN_LEN,
)
}
#[must_use]
pub(crate) const fn input(self) -> RuntimeInputLimits {
self.input
}
#[must_use]
pub(crate) const fn execution(self) -> ExecutionLimits {
self.execution
}
}
pub(crate) fn runtime_input(
bytes: &[u8],
limits: RuntimeInputLimits,
) -> Result<RuntimeInput, RuntimeInputError> {
RuntimeInput::validate(RuntimeInputSource::from_bytes(bytes), limits)
}
pub(crate) fn run_seed(bytes: &[u8], policy: TestRunPolicy) -> Result<RunSeed, TestFailure> {
Ok(RunSeed::admit(
runtime_input(bytes, policy.input())?,
policy.execution(),
)?)
}
pub(crate) fn parse_program(source: &str) -> Result<Program, ParseError> {
Program::parse(ProgramSource::from_text(source), DEFAULT_PARSE_LIMITS)
}
pub(crate) fn parse_program_bytes(source: &[u8]) -> Result<Program, ParseError> {
Program::parse(ProgramSource::from_bytes(source), DEFAULT_PARSE_LIMITS)
}
pub(crate) fn ensure(condition: bool, message: impl Into<String>) -> TestResult {
if condition {
Ok(())
} else {
Err(TestFailure::message(message))
}
}
pub(crate) fn ensure_matches(condition: bool, message: &'static str) -> TestResult {
ensure(condition, message)
}
macro_rules! ensure_eq {
($actual:expr, $expected:expr $(,)?) => {{
match (&$actual, &$expected) {
(actual, expected) => {
if *actual == *expected {
Ok(())
} else {
Err($crate::test_support::TestFailure::message(::std::format!(
"values differed\nactual: {actual:?}\nexpected: {expected:?}",
)))
}
}
}
}};
}
pub(crate) use ensure_eq;
pub(crate) fn expect_parse_error(source: &str) -> Result<ParseError, TestFailure> {
match parse_program(source) {
Ok(_) => Err(TestFailure::message("expected parse error")),
Err(error) => Ok(error),
}
}
pub(crate) fn expect_error_position(error: &ParseError, line: usize, column: usize) -> TestResult {
let line = source_line_number(line)?;
let column = source_column(column)?;
ensure_eq!(
error.location(),
ParseErrorLocation::Position(SourcePosition::new(line, column)),
)
}
pub(crate) fn source_line_number(one_based: usize) -> Result<SourceLineNumber, TestFailure> {
let zero_based = one_based
.checked_sub(1)
.ok_or(TestFailure::message("expected non-zero source line"))?;
SourceLineNumber::from_zero_based(zero_based)
.ok_or(TestFailure::message("expected representable source line"))
}
pub(crate) fn source_column(one_based: usize) -> Result<SourceColumn, TestFailure> {
let zero_based = one_based
.checked_sub(1)
.ok_or(TestFailure::message("expected non-zero source column"))?;
SourceColumn::from_zero_based(zero_based)
.ok_or(TestFailure::message("expected representable source column"))
}