wsb 0.255.6379

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! A test that exists, as a row.
//!
//! This is the identity of a test — where it is written and what it claims —
//! and deliberately not how it went when it last ran. That is `test_runs`, a
//! separate table, because a test that has never been run still exists and a
//! catalogue that had to invent a verdict for it would be recording a guess.

use serde::{Deserialize, Serialize};

/// The language a test is written in.
///
/// A closed set: the database refuses any other value, so a scanner that grew a
/// new extension has to say what language it found rather than writing a string
/// nobody queries for.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TestLanguage {
    Rust,
    Go,
    Python,
    Js,
    Swift,
    Objc,
    Shell,
}

impl TestLanguage {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Rust => "rust",
            Self::Go => "go",
            Self::Python => "python",
            Self::Js => "js",
            Self::Swift => "swift",
            Self::Objc => "objc",
            Self::Shell => "shell",
        }
    }

    pub fn parse(value: &str) -> Result<Self, String> {
        Ok(match value {
            "rust" => Self::Rust,
            "go" => Self::Go,
            "python" => Self::Python,
            "js" => Self::Js,
            "swift" => Self::Swift,
            "objc" => Self::Objc,
            "shell" => Self::Shell,
            other => return Err(format!("'{other}' is not a language the catalogue knows")),
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CataloguedTest {
    pub id: String,
    /// The repository, relative to the workspace root. The superproject is `""`.
    pub repo: String,
    pub language: TestLanguage,
    /// The `TESTnnnn` this test claims, or `None` when it claims none.
    pub number: Option<i64>,
    pub function_name: String,
    /// Empty when the test carries none — never `None`, because "has no
    /// description" and "was not looked at" are not the same fact and this
    /// table can only report the first.
    pub description: String,
    /// Relative to the repository root.
    pub file_path: String,
    pub line_number: i64,
    /// The feature whose number this test claims, when one claims it.
    pub feature_id: Option<String>,
}

impl CataloguedTest {
    /// Where the test is, in the form an editor and a person both accept.
    pub fn location(&self) -> String {
        format!("{}:{}", self.file_path, self.line_number)
    }

    /// The number the DESCRIPTION claims, which is not always the number the
    /// name claims. A test called `test8420_…` described as `TEST8402: …` is
    /// one of the two renumbered and the other missed, and only comparing them
    /// finds it.
    pub fn described_number(&self) -> Option<i64> {
        let rest = self.description.trim().strip_prefix("TEST")?;
        let digits: String = rest.chars().take_while(char::is_ascii_digit).collect();
        if digits.is_empty() {
            return None;
        }
        // Only a description that goes on to say something counts: `TEST8420`
        // alone in a comment is a reference to a test, not a claim to be one.
        let after = rest[digits.len()..].trim_start();
        if !after.starts_with(':') && !after.starts_with('—') && !after.starts_with('-') {
            return None;
        }
        digits.parse().ok()
    }

    /// True when the name and the description claim different numbers.
    pub fn is_misnumbered(&self) -> bool {
        match (self.number, self.described_number()) {
            (Some(name), Some(described)) => name != described,
            _ => false,
        }
    }
}