caexfer 0.1.0

Exchange linear finite-element meshes and results across CAE formats
Documentation
//! Exercise the two public file operations from an external crate.

use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};

use caexfer::{AssumptionKind, MshVersion, Options, Stage, convert, validate};

static NEXT: AtomicU64 = AtomicU64::new(0);

struct Scratch(PathBuf);

impl Scratch {
    fn new() -> Self {
        let path = std::env::temp_dir().join(format!(
            "caexfer-api-{}-{}",
            std::process::id(),
            NEXT.fetch_add(1, Ordering::Relaxed)
        ));
        std::fs::create_dir(&path).unwrap();
        Self(path)
    }

    fn path(&self, name: &str) -> PathBuf {
        self.0.join(name)
    }
}

impl Drop for Scratch {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.0);
    }
}

fn fixture(name: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join(name)
}

#[test]
fn validation_matches_cli_strictness_and_counts() {
    let source = fixture("examples/plate.bdf");
    let ordinary = validate(&source, &Options::default()).unwrap();
    assert!(ordinary.passed);
    assert_eq!(
        (ordinary.points, ordinary.cells, ordinary.fields),
        (4, 1, 0)
    );
    assert_ne!(ordinary.omissions.len(), 0);
    let strict = validate(
        &source,
        &Options {
            strict: true,
            ..Options::default()
        },
    )
    .unwrap();
    assert!(!strict.passed);
    assert_eq!(strict.omissions, ordinary.omissions);
}

#[test]
fn conversion_requires_acceptance_and_never_overwrites() {
    let scratch = Scratch::new();
    let output = scratch.path("model.vtu");
    let source = fixture("examples/plate.bdf");
    let error = convert(&source, &output, &Options::default()).unwrap_err();
    assert_eq!(error.code, "E_USAGE");
    assert!(error.message.contains("accept_omissions"));
    assert!(!output.exists());
    let report = convert(
        &source,
        &output,
        &Options {
            accept_omissions: true,
            ..Options::default()
        },
    )
    .unwrap();
    assert_eq!((report.points, report.cells), (4, 1));
    assert_ne!(report.omissions.len(), 0);
    assert!(
        std::fs::read_to_string(&output)
            .unwrap()
            .contains("<VTKFile")
    );
    assert_eq!(
        convert(
            &source,
            &output,
            &Options {
                accept_omissions: true,
                ..Options::default()
            }
        )
        .unwrap_err()
        .code,
        "E_EXISTS"
    );
}

#[test]
fn op2_companion_is_staged_and_reported() {
    let scratch = Scratch::new();
    let output = scratch.path("results.op2");
    let mesh = scratch.path("mesh.bdf");
    let options = Options {
        mesh_output: Some(mesh.clone()),
        accept_all: true,
        ..Options::default()
    };
    let report = convert(fixture("examples/plate.bdf"), &output, &options).unwrap();
    let companion = report.mesh_output.unwrap();
    assert_eq!(companion.path, mesh);
    assert!(output.exists());
    assert!(companion.path.exists());
    let reread = validate(
        &output,
        &Options {
            mesh: Some(companion.path),
            ..Options::default()
        },
    )
    .unwrap();
    assert!(reread.passed);
    assert_eq!(reread.points, 4);
}

#[test]
fn validation_reports_unverified_frame_without_acceptance() {
    let scratch = Scratch::new();
    let source = fixture("examples/plate.bdf");
    let op2 = scratch.path("results.op2");
    let mesh = scratch.path("mesh.vtu");
    let accepted = Options {
        accept_all: true,
        ..Options::default()
    };
    convert(&source, &op2, &accepted).unwrap();
    convert(&source, &mesh, &accepted).unwrap();

    let options = Options {
        mesh: Some(mesh),
        ..Options::default()
    };
    let report = validate(&op2, &options).unwrap();
    assert!(report.passed);
    assert!(report.omissions.iter().any(|item| {
        item.stage == Stage::Assumption && item.assumption == Some(AssumptionKind::BasicFrame)
    }));
    let strict = validate(
        &op2,
        &Options {
            strict: true,
            ..options
        },
    )
    .unwrap();
    assert!(!strict.passed);
}

#[test]
fn companion_output_requires_op2_destination() {
    let scratch = Scratch::new();
    let output = scratch.path("result.vtu");
    let companion = scratch.path("mesh.bdf");
    let error = convert(
        fixture("examples/plate.bdf"),
        &output,
        &Options {
            mesh_output: Some(companion.clone()),
            accept_all: true,
            ..Options::default()
        },
    )
    .unwrap_err();
    assert_eq!(error.code, "E_USAGE");
    assert!(!output.exists() && !companion.exists());
}

#[test]
fn op2_companion_requires_a_mesh_bearing_format() {
    let scratch = Scratch::new();
    let output = scratch.path("results.op2");
    let companion = scratch.path("companion.op2");
    let error = convert(
        fixture("examples/plate.bdf"),
        &output,
        &Options {
            mesh_output: Some(companion.clone()),
            accept_all: true,
            ..Options::default()
        },
    )
    .unwrap_err();
    assert_eq!(error.code, "E_USAGE");
    assert!(!output.exists() && !companion.exists());
}

#[test]
fn documented_frd_msh_and_pch_routes_use_only_the_root_api() {
    let scratch = Scratch::new();
    let vtu = scratch.path("results.vtu");
    let msh = scratch.path("results.msh");
    let pch_vtu = scratch.path("displacements.vtu");
    let accepted = Options {
        accept_omissions: true,
        ..Options::default()
    };
    convert(
        fixture("tests/fixtures/linear-results.frd"),
        &vtu,
        &accepted,
    )
    .unwrap();
    convert(
        &vtu,
        &msh,
        &Options {
            msh_version: Some(MshVersion::V2_2),
            ..accepted.clone()
        },
    )
    .unwrap();
    let msh_text = std::fs::read_to_string(&msh).unwrap();
    assert!(msh_text.contains("2.2 0 8"));
    let report = convert(
        fixture("tests/fixtures/pch-multiple.pch"),
        &pch_vtu,
        &Options {
            mesh: Some(fixture("tests/fixtures/pch-companion.bdf")),
            subcase: Some(1),
            ..accepted
        },
    )
    .unwrap();
    assert_eq!(report.fields, 1);
}