mod common;
use std::io::Write;
use std::process::Stdio;
use common::*;
use logicaffeine_language::{compile, CompileOptions, OutputFormat};
use tempfile::tempdir;
const SENTENCE: &str = "Every cat sleeps.";
fn oracle(format: OutputFormat) -> String {
logicaffeine_language::compile::compile_with_options(
SENTENCE,
CompileOptions { format, pragmatic: false },
)
.expect("oracle sentence must compile")
}
#[test]
fn inline_sentence_matches_library() {
let out = largo().args(["logic", SENTENCE]).output().unwrap();
assert_eq!(out.status.code(), Some(0), "logic: {}", stderr(&out));
assert_eq!(stdout(&out).trim_end(), compile(SENTENCE).unwrap());
}
#[test]
fn formats_match_library_options() {
for (flag, format, must_contain) in [
("unicode", OutputFormat::Unicode, "∀"),
("latex", OutputFormat::LaTeX, "\\forall"),
("ascii", OutputFormat::SimpleFOL, "("),
("kripke", OutputFormat::Kripke, "("),
] {
let out = largo()
.args(["logic", SENTENCE, "--format", flag])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(0), "--format {flag}: {}", stderr(&out));
let got = stdout(&out);
let got = got.trim_end();
assert_eq!(got, oracle(format), "--format {flag} must match the library");
assert!(got.contains(must_contain), "--format {flag} sanity: {got}");
}
}
#[test]
fn file_input_works() {
let dir = tempdir().unwrap();
let path = dir.path().join("sentence.txt");
std::fs::write(&path, SENTENCE).unwrap();
let out = largo()
.args(["logic", "--file", path.to_str().unwrap()])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
assert_eq!(stdout(&out).trim_end(), compile(SENTENCE).unwrap());
}
#[test]
fn stdin_input_works() {
let mut child = largo()
.arg("logic")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.as_mut()
.unwrap()
.write_all(SENTENCE.as_bytes())
.unwrap();
let out = child.wait_with_output().unwrap();
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
assert_eq!(stdout(&out).trim_end(), compile(SENTENCE).unwrap());
}
#[test]
fn empty_input_is_usage_error() {
let mut child = largo()
.arg("logic")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
drop(child.stdin.take());
let out = child.wait_with_output().unwrap();
assert_eq!(out.status.code(), Some(2), "empty input must be a usage error");
}
#[test]
fn all_readings_match_library() {
let ambiguous = "Every woman loves a man.";
let opts = CompileOptions { format: OutputFormat::Unicode, pragmatic: false };
let mut expected: Vec<String> = Vec::new();
for r in logicaffeine_language::compile::compile_all_scopes_with_options(ambiguous, opts)
.unwrap_or_default()
{
if !expected.contains(&r) {
expected.push(r);
}
}
for r in logicaffeine_language::compile::compile_forest_with_options(ambiguous, opts) {
if !expected.contains(&r) {
expected.push(r);
}
}
assert!(expected.len() >= 2, "oracle sentence must be ambiguous");
let out = largo()
.args(["logic", ambiguous, "--all-readings"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
let text = stdout(&out);
for (i, reading) in expected.iter().enumerate() {
let line = format!("{}. {}", i + 1, reading);
assert!(text.contains(&line), "missing reading {line:?} in:\n{text}");
}
}
#[test]
fn discourse_matches_library() {
let lines = ["A farmer owns a donkey.", "He feeds it."];
let expected = logicaffeine_language::compile::compile_discourse_with_options(
&lines,
CompileOptions::default(),
)
.expect("discourse oracle must compile");
let mut child = largo()
.args(["logic", "--discourse"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.as_mut()
.unwrap()
.write_all(lines.join("\n").as_bytes())
.unwrap();
let out = child.wait_with_output().unwrap();
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
assert_eq!(stdout(&out).trim_end(), expected);
}
#[test]
fn pragmatic_matches_library() {
let scalar = "Some cats sleep.";
let expected = logicaffeine_language::compile::compile_pragmatic(scalar)
.expect("pragmatic oracle must compile");
let out = largo().args(["logic", scalar, "--pragmatic"]).output().unwrap();
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
assert_eq!(stdout(&out).trim_end(), expected);
}
#[test]
fn parse_error_reports_socratically() {
let out = largo().args(["logic", "the the the."]).output().unwrap();
assert_eq!(out.status.code(), Some(1), "garbage must fail");
assert_eq!(stdout(&out), "", "stdout stays clean on error");
assert!(!stderr(&out).trim().is_empty(), "stderr must explain");
}
#[test]
fn piped_fol_has_no_ansi() {
let out = largo().args(["logic", SENTENCE]).output().unwrap();
assert!(!has_ansi(&stdout(&out)), "no ANSI in piped FOL");
}
#[test]
fn discourse_conflicts_with_all_readings() {
let out = largo()
.args(["logic", SENTENCE, "--discourse", "--all-readings"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(2));
}