use clap::ValueEnum;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum Confidence {
High,
#[default]
Medium,
Low,
}
impl std::fmt::Display for Confidence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::High => write!(f, "high"),
Self::Medium => write!(f, "medium"),
Self::Low => write!(f, "low"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum DocstringStyle {
Google,
Numpy,
Sphinx,
#[default]
Plain,
}
impl std::fmt::Display for DocstringStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Google => write!(f, "google"),
Self::Numpy => write!(f, "numpy"),
Self::Sphinx => write!(f, "sphinx"),
Self::Plain => write!(f, "plain"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectType {
Io,
GlobalWrite,
AttributeWrite,
CollectionModify,
Call,
UnknownCall,
}
impl std::fmt::Display for EffectType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io => write!(f, "io"),
Self::GlobalWrite => write!(f, "global_write"),
Self::AttributeWrite => write!(f, "attribute_write"),
Self::CollectionModify => write!(f, "collection_modify"),
Self::Call => write!(f, "call"),
Self::UnknownCall => write!(f, "unknown_call"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConditionSource {
Guard,
Docstring,
TypeHint,
Assertion,
Icontract,
Deal,
}
impl std::fmt::Display for ConditionSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Guard => write!(f, "guard"),
Self::Docstring => write!(f, "docstring"),
Self::TypeHint => write!(f, "type_hint"),
Self::Assertion => write!(f, "assertion"),
Self::Icontract => write!(f, "icontract"),
Self::Deal => write!(f, "deal"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CohesionVerdict {
Cohesive,
SplitCandidate,
}
impl CohesionVerdict {
pub fn from_lcom4(lcom4: u32) -> Self {
if lcom4 <= 1 {
Self::Cohesive
} else {
Self::SplitCandidate
}
}
}
impl std::fmt::Display for CohesionVerdict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Cohesive => write!(f, "cohesive"),
Self::SplitCandidate => write!(f, "split_candidate"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ComponentInfo {
pub methods: Vec<String>,
pub fields: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClassCohesion {
pub class_name: String,
pub file_path: String,
pub line: u32,
pub lcom4: u32,
pub method_count: u32,
pub field_count: u32,
pub verdict: CohesionVerdict,
pub split_suggestion: Option<String>,
pub components: Vec<ComponentInfo>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CohesionSummary {
pub total_classes: u32,
pub cohesive: u32,
pub split_candidates: u32,
pub avg_lcom4: f64,
}
impl Default for CohesionSummary {
fn default() -> Self {
Self {
total_classes: 0,
cohesive: 0,
split_candidates: 0,
avg_lcom4: 0.0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CohesionReport {
pub classes: Vec<ClassCohesion>,
pub summary: CohesionSummary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CouplingVerdict {
Low,
Moderate,
High,
VeryHigh,
}
impl CouplingVerdict {
pub fn from_score(score: f64) -> Self {
if score < 0.2 {
Self::Low
} else if score < 0.4 {
Self::Moderate
} else if score < 0.6 {
Self::High
} else {
Self::VeryHigh
}
}
}
impl std::fmt::Display for CouplingVerdict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Low => write!(f, "low"),
Self::Moderate => write!(f, "moderate"),
Self::High => write!(f, "high"),
Self::VeryHigh => write!(f, "very_high"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CrossCall {
pub caller: String,
pub callee: String,
pub line: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct CrossCalls {
pub calls: Vec<CrossCall>,
pub count: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CouplingReport {
pub path_a: String,
pub path_b: String,
pub a_to_b: CrossCalls,
pub b_to_a: CrossCalls,
pub total_calls: u32,
pub coupling_score: f64,
pub verdict: CouplingVerdict,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionPurity {
pub name: String,
pub classification: String,
pub effects: Vec<String>,
pub confidence: Confidence,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FilePurityReport {
pub source_file: String,
pub functions: Vec<FunctionPurity>,
pub pure_count: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PurityReport {
pub files: Vec<FilePurityReport>,
pub total_functions: u32,
pub total_pure: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TemporalExample {
pub file: String,
pub line: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemporalConstraint {
pub before: String,
pub after: String,
pub support: u32,
pub confidence: f64,
pub examples: Vec<TemporalExample>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Trigram {
pub sequence: [String; 3],
pub support: u32,
pub confidence: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemporalMetadata {
pub files_analyzed: u32,
pub sequences_extracted: u32,
pub min_support: u32,
pub min_confidence: f64,
}
impl Default for TemporalMetadata {
fn default() -> Self {
Self {
files_analyzed: 0,
sequences_extracted: 0,
min_support: 2,
min_confidence: 0.5,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemporalReport {
pub constraints: Vec<TemporalConstraint>,
pub trigrams: Vec<Trigram>,
pub metadata: TemporalMetadata,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FunctionInfo {
pub name: String,
pub signature: String,
pub docstring: Option<String>,
pub lineno: u32,
pub is_async: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MethodInfo {
pub name: String,
pub signature: String,
pub is_async: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClassInfo {
pub name: String,
pub lineno: u32,
pub bases: Vec<String>,
pub methods: Vec<MethodInfo>,
pub private_method_count: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct InterfaceInfo {
pub file: String,
pub all_exports: Option<Vec<String>>,
pub functions: Vec<FunctionInfo>,
pub classes: Vec<ClassInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceInfo {
pub name: String,
pub resource_type: String,
pub line: u32,
pub closed: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LeakInfo {
pub resource: String,
pub line: u32,
pub paths: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DoubleCloseInfo {
pub resource: String,
pub first_close: u32,
pub second_close: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UseAfterCloseInfo {
pub resource: String,
pub close_line: u32,
pub use_line: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContextSuggestion {
pub resource: String,
pub suggestion: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceConstraint {
pub rule: String,
pub context: String,
pub confidence: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ResourceSummary {
pub resources_detected: u32,
pub leaks_found: u32,
pub double_closes_found: u32,
pub use_after_closes_found: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceReport {
pub file: String,
pub language: String,
pub function: Option<String>,
pub resources: Vec<ResourceInfo>,
pub leaks: Vec<LeakInfo>,
pub double_closes: Vec<DoubleCloseInfo>,
pub use_after_closes: Vec<UseAfterCloseInfo>,
pub suggestions: Vec<ContextSuggestion>,
pub constraints: Vec<ResourceConstraint>,
pub summary: ResourceSummary,
pub analysis_time_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Precondition {
pub param: String,
pub expression: Option<String>,
pub description: Option<String>,
pub type_hint: Option<String>,
pub source: ConditionSource,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Postcondition {
pub expression: Option<String>,
pub description: Option<String>,
pub type_hint: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExceptionInfo {
pub exception_type: String,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct YieldInfo {
pub type_hint: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SideEffect {
pub effect_type: EffectType,
pub target: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionBehavior {
pub function_name: String,
pub file_path: String,
pub line: u32,
pub purity_classification: String,
pub is_generator: bool,
pub is_async: bool,
pub preconditions: Vec<Precondition>,
pub postconditions: Vec<Postcondition>,
pub exceptions: Vec<ExceptionInfo>,
pub yields: Vec<YieldInfo>,
pub side_effects: Vec<SideEffect>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ClassInvariant {
pub expression: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClassBehavior {
pub class_name: String,
pub invariants: Vec<ClassInvariant>,
pub methods: Vec<FunctionBehavior>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BehavioralReport {
pub file_path: String,
pub docstring_style: DocstringStyle,
pub has_icontract: bool,
pub has_deal: bool,
pub functions: Vec<FunctionBehavior>,
pub classes: Vec<ClassBehavior>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VariableMutability {
pub name: String,
pub mutable: bool,
pub reassignments: u32,
pub mutations: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParameterMutability {
pub name: String,
pub mutated: bool,
pub mutation_sites: Vec<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CollectionMutation {
pub variable: String,
pub operation: String,
pub line: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionMutability {
pub name: String,
pub variables: Vec<VariableMutability>,
pub parameters: Vec<ParameterMutability>,
pub collection_mutations: Vec<CollectionMutation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FieldMutability {
pub name: String,
pub mutable: bool,
pub init_only: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClassMutability {
pub name: String,
pub fields: Vec<FieldMutability>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MutabilitySummary {
pub functions_analyzed: u32,
pub classes_analyzed: u32,
pub total_variables: u32,
pub mutable_variables: u32,
pub immutable_variables: u32,
pub immutable_pct: f64,
pub parameters_analyzed: u32,
pub mutated_parameters: u32,
pub unmutated_pct: f64,
pub fields_analyzed: u32,
pub mutable_fields: u32,
pub constraints_generated: u32,
}
impl Default for MutabilitySummary {
fn default() -> Self {
Self {
functions_analyzed: 0,
classes_analyzed: 0,
total_variables: 0,
mutable_variables: 0,
immutable_variables: 0,
immutable_pct: 0.0,
parameters_analyzed: 0,
mutated_parameters: 0,
unmutated_pct: 0.0,
fields_analyzed: 0,
mutable_fields: 0,
constraints_generated: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MutabilityReport {
pub file: String,
pub language: String,
pub functions: Vec<FunctionMutability>,
pub classes: Vec<ClassMutability>,
pub summary: MutabilitySummary,
pub analysis_time_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
pub enum OutputFormat {
#[default]
Json,
Text,
}
impl std::fmt::Display for OutputFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Json => write!(f, "json"),
Self::Text => write!(f, "text"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_confidence_enum_serialization() {
let json = serde_json::to_string(&Confidence::High).unwrap();
assert_eq!(json, r#""high""#);
let json = serde_json::to_string(&Confidence::Medium).unwrap();
assert_eq!(json, r#""medium""#);
let json = serde_json::to_string(&Confidence::Low).unwrap();
assert_eq!(json, r#""low""#);
}
#[test]
fn test_confidence_enum_deserialization() {
let high: Confidence = serde_json::from_str(r#""high""#).unwrap();
assert_eq!(high, Confidence::High);
let medium: Confidence = serde_json::from_str(r#""medium""#).unwrap();
assert_eq!(medium, Confidence::Medium);
let low: Confidence = serde_json::from_str(r#""low""#).unwrap();
assert_eq!(low, Confidence::Low);
}
#[test]
fn test_confidence_display() {
assert_eq!(Confidence::High.to_string(), "high");
assert_eq!(Confidence::Medium.to_string(), "medium");
assert_eq!(Confidence::Low.to_string(), "low");
}
#[test]
fn test_confidence_default() {
assert_eq!(Confidence::default(), Confidence::Medium);
}
#[test]
fn test_docstring_style_serialization() {
let json = serde_json::to_string(&DocstringStyle::Google).unwrap();
assert_eq!(json, r#""google""#);
let json = serde_json::to_string(&DocstringStyle::Numpy).unwrap();
assert_eq!(json, r#""numpy""#);
let json = serde_json::to_string(&DocstringStyle::Sphinx).unwrap();
assert_eq!(json, r#""sphinx""#);
let json = serde_json::to_string(&DocstringStyle::Plain).unwrap();
assert_eq!(json, r#""plain""#);
}
#[test]
fn test_docstring_style_display() {
assert_eq!(DocstringStyle::Google.to_string(), "google");
assert_eq!(DocstringStyle::Numpy.to_string(), "numpy");
assert_eq!(DocstringStyle::Sphinx.to_string(), "sphinx");
assert_eq!(DocstringStyle::Plain.to_string(), "plain");
}
#[test]
fn test_effect_type_serialization() {
let json = serde_json::to_string(&EffectType::Io).unwrap();
assert_eq!(json, r#""io""#);
let json = serde_json::to_string(&EffectType::GlobalWrite).unwrap();
assert_eq!(json, r#""global_write""#);
let json = serde_json::to_string(&EffectType::AttributeWrite).unwrap();
assert_eq!(json, r#""attribute_write""#);
let json = serde_json::to_string(&EffectType::CollectionModify).unwrap();
assert_eq!(json, r#""collection_modify""#);
let json = serde_json::to_string(&EffectType::Call).unwrap();
assert_eq!(json, r#""call""#);
}
#[test]
fn test_condition_source_serialization() {
let json = serde_json::to_string(&ConditionSource::Guard).unwrap();
assert_eq!(json, r#""guard""#);
let json = serde_json::to_string(&ConditionSource::Docstring).unwrap();
assert_eq!(json, r#""docstring""#);
let json = serde_json::to_string(&ConditionSource::TypeHint).unwrap();
assert_eq!(json, r#""type_hint""#);
let json = serde_json::to_string(&ConditionSource::Assertion).unwrap();
assert_eq!(json, r#""assertion""#);
let json = serde_json::to_string(&ConditionSource::Icontract).unwrap();
assert_eq!(json, r#""icontract""#);
let json = serde_json::to_string(&ConditionSource::Deal).unwrap();
assert_eq!(json, r#""deal""#);
}
#[test]
fn test_cohesion_verdict_from_lcom4() {
assert_eq!(CohesionVerdict::from_lcom4(0), CohesionVerdict::Cohesive);
assert_eq!(CohesionVerdict::from_lcom4(1), CohesionVerdict::Cohesive);
assert_eq!(
CohesionVerdict::from_lcom4(2),
CohesionVerdict::SplitCandidate
);
assert_eq!(
CohesionVerdict::from_lcom4(5),
CohesionVerdict::SplitCandidate
);
}
#[test]
fn test_coupling_verdict_from_score() {
assert_eq!(CouplingVerdict::from_score(0.0), CouplingVerdict::Low);
assert_eq!(CouplingVerdict::from_score(0.1), CouplingVerdict::Low);
assert_eq!(CouplingVerdict::from_score(0.2), CouplingVerdict::Moderate);
assert_eq!(CouplingVerdict::from_score(0.3), CouplingVerdict::Moderate);
assert_eq!(CouplingVerdict::from_score(0.4), CouplingVerdict::High);
assert_eq!(CouplingVerdict::from_score(0.5), CouplingVerdict::High);
assert_eq!(CouplingVerdict::from_score(0.6), CouplingVerdict::VeryHigh);
assert_eq!(CouplingVerdict::from_score(1.0), CouplingVerdict::VeryHigh);
}
#[test]
fn test_class_cohesion_serialization() {
let cohesion = ClassCohesion {
class_name: "MyClass".to_string(),
file_path: "test.py".to_string(),
line: 10,
lcom4: 2,
method_count: 4,
field_count: 3,
verdict: CohesionVerdict::SplitCandidate,
split_suggestion: Some("Consider splitting".to_string()),
components: vec![ComponentInfo {
methods: vec!["method1".to_string()],
fields: vec!["field1".to_string()],
}],
};
let json = serde_json::to_string(&cohesion).unwrap();
let parsed: ClassCohesion = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.class_name, "MyClass");
assert_eq!(parsed.lcom4, 2);
}
#[test]
fn test_coupling_report_serialization() {
let report = CouplingReport {
path_a: "module_a.py".to_string(),
path_b: "module_b.py".to_string(),
a_to_b: CrossCalls {
calls: vec![CrossCall {
caller: "func_a".to_string(),
callee: "func_b".to_string(),
line: 10,
}],
count: 1,
},
b_to_a: CrossCalls::default(),
total_calls: 1,
coupling_score: 0.1,
verdict: CouplingVerdict::Low,
};
let json = serde_json::to_string(&report).unwrap();
let parsed: CouplingReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.coupling_score, 0.1);
}
#[test]
fn test_purity_report_serialization() {
let report = PurityReport {
files: vec![FilePurityReport {
source_file: "test.py".to_string(),
functions: vec![FunctionPurity {
name: "pure_func".to_string(),
classification: "pure".to_string(),
effects: vec![],
confidence: Confidence::High,
}],
pure_count: 1,
}],
total_functions: 1,
total_pure: 1,
};
let json = serde_json::to_string(&report).unwrap();
let parsed: PurityReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.total_pure, 1);
}
#[test]
fn test_temporal_report_serialization() {
let report = TemporalReport {
constraints: vec![TemporalConstraint {
before: "open".to_string(),
after: "close".to_string(),
support: 5,
confidence: 0.9,
examples: vec![TemporalExample {
file: "test.py".to_string(),
line: 10,
}],
}],
trigrams: vec![],
metadata: TemporalMetadata::default(),
};
let json = serde_json::to_string(&report).unwrap();
let parsed: TemporalReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.constraints[0].before, "open");
}
#[test]
fn test_interface_info_serialization() {
let info = InterfaceInfo {
file: "test.py".to_string(),
all_exports: Some(vec!["func1".to_string()]),
functions: vec![FunctionInfo {
name: "func1".to_string(),
signature: "def func1(x: int) -> str".to_string(),
docstring: Some("A function".to_string()),
lineno: 5,
is_async: false,
}],
classes: vec![],
};
let json = serde_json::to_string(&info).unwrap();
let parsed: InterfaceInfo = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.functions[0].name, "func1");
}
#[test]
fn test_resource_report_serialization() {
let report = ResourceReport {
file: "test.py".to_string(),
language: "python".to_string(),
function: Some("process".to_string()),
resources: vec![ResourceInfo {
name: "f".to_string(),
resource_type: "file".to_string(),
line: 10,
closed: true,
}],
leaks: vec![],
double_closes: vec![],
use_after_closes: vec![],
suggestions: vec![],
constraints: vec![],
summary: ResourceSummary::default(),
analysis_time_ms: 50,
};
let json = serde_json::to_string(&report).unwrap();
let parsed: ResourceReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.resources[0].name, "f");
}
#[test]
fn test_behavioral_report_serialization() {
let report = BehavioralReport {
file_path: "test.py".to_string(),
docstring_style: DocstringStyle::Google,
has_icontract: false,
has_deal: false,
functions: vec![FunctionBehavior {
function_name: "validate".to_string(),
file_path: "test.py".to_string(),
line: 10,
purity_classification: "pure".to_string(),
is_generator: false,
is_async: false,
preconditions: vec![Precondition {
param: "x".to_string(),
expression: Some("x > 0".to_string()),
description: None,
type_hint: Some("int".to_string()),
source: ConditionSource::Guard,
}],
postconditions: vec![],
exceptions: vec![],
yields: vec![],
side_effects: vec![],
}],
classes: vec![],
};
let json = serde_json::to_string(&report).unwrap();
let parsed: BehavioralReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.functions[0].function_name, "validate");
}
#[test]
fn test_mutability_report_serialization() {
let report = MutabilityReport {
file: "test.py".to_string(),
language: "python".to_string(),
functions: vec![FunctionMutability {
name: "process".to_string(),
variables: vec![VariableMutability {
name: "count".to_string(),
mutable: true,
reassignments: 3,
mutations: 0,
}],
parameters: vec![],
collection_mutations: vec![],
}],
classes: vec![],
summary: MutabilitySummary::default(),
analysis_time_ms: 30,
};
let json = serde_json::to_string(&report).unwrap();
let parsed: MutabilityReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.functions[0].name, "process");
}
#[test]
fn test_output_format_display() {
assert_eq!(OutputFormat::Json.to_string(), "json");
assert_eq!(OutputFormat::Text.to_string(), "text");
}
#[test]
fn test_output_format_default() {
assert_eq!(OutputFormat::default(), OutputFormat::Json);
}
}