tectonic/tests/tex-outputs.rs

366 lines
9.5 KiB
Rust

// Copyright 2016-2021 the Tectonic Project
// Licensed under the MIT License.
use std::collections::HashSet;
use std::path::Path;
use std::time;
use tectonic::engines::tex::TexOutcome;
use tectonic::errors::DefinitelySame;
use tectonic::io::testing::SingleInputFileIo;
use tectonic::io::{FilesystemIo, FilesystemPrimaryInputIo, IoProvider, IoStack, MemoryIo};
use tectonic::unstable_opts::UnstableOptions;
use tectonic::{TexEngine, XdvipdfmxEngine};
use tectonic_bridge_core::{CoreBridgeLauncher, MinimalDriver};
use tectonic_errors::{anyhow::anyhow, Result};
use tectonic_status_base::NoopStatusBackend;
#[path = "util/mod.rs"]
mod util;
use crate::util::{ensure_plain_format, test_path, Expected, ExpectedFile};
struct TestCase {
stem: String,
expected_result: Result<TexOutcome>,
check_synctex: bool,
check_pdf: bool,
extra_io: Vec<Box<dyn IoProvider>>,
unstables: UnstableOptions,
}
impl TestCase {
fn new(stem: &str) -> Self {
TestCase {
stem: stem.to_owned(),
expected_result: Ok(TexOutcome::Spotless),
check_synctex: false,
check_pdf: false,
extra_io: Vec::new(),
unstables: UnstableOptions::default(),
}
}
fn check_synctex(mut self, check_synctex: bool) -> Self {
self.check_synctex = check_synctex;
self
}
fn check_pdf(mut self, check_pdf: bool) -> Self {
self.check_pdf = check_pdf;
self
}
fn with_fs(mut self, path: &Path) -> Self {
self.extra_io.push(Box::new(FilesystemIo::new(
path,
false,
false,
HashSet::new(),
)));
self
}
fn with_unstables(mut self, unstables: UnstableOptions) -> Self {
self.unstables = unstables;
self
}
fn expect(mut self, result: Result<TexOutcome>) -> Self {
self.expected_result = result;
self
}
fn expect_msg(self, msg: &str) -> Self {
self.expect(Err(anyhow!("{}", msg)))
}
fn go(mut self) {
util::set_test_root();
let expect_xdv = self.expected_result.is_ok();
let mut p = test_path(&[]);
// IoProvider for the format file; with magic to generate the format
// on-the-fly if needed.
let mut fmt =
SingleInputFileIo::new(&ensure_plain_format().expect("couldn't write format file"));
// Set up some useful paths, and the IoProvider for the primary input file.
p.push("tex-outputs");
p.push(&self.stem);
p.set_extension("tex");
let texname = p.file_name().unwrap().to_str().unwrap().to_owned();
let mut tex = FilesystemPrimaryInputIo::new(&p);
p.set_extension("xdv");
let xdvname = p.file_name().unwrap().to_str().unwrap().to_owned();
p.set_extension("pdf");
let pdfname = p.file_name().unwrap().to_str().unwrap().to_owned();
// MemoryIo layer that will accept the outputs.
let mut mem = MemoryIo::new(true);
// We only need the assets when running xdvipdfmx, but due to how
// ownership works with IoStacks, it's easier to just unconditionally
// add this layer.
let mut assets = FilesystemIo::new(&test_path(&["assets"]), false, false, HashSet::new());
let expected_log = ExpectedFile::read_with_extension(&mut p, "log");
// Run the engine(s)!
let res = {
let mut io_list: Vec<&mut dyn IoProvider> =
vec![&mut mem, &mut tex, &mut fmt, &mut assets];
for io in &mut self.extra_io {
io_list.push(&mut **io);
}
let io = IoStack::new(io_list);
let mut hooks = MinimalDriver::new(io);
let mut status = NoopStatusBackend::default();
let mut launcher = CoreBridgeLauncher::new(&mut hooks, &mut status);
let tex_res = TexEngine::default()
.shell_escape(self.unstables.shell_escape)
.process(&mut launcher, "plain.fmt", &texname);
if self.check_pdf && tex_res.definitely_same(&self.expected_result) {
let mut engine = XdvipdfmxEngine::default();
engine
.enable_compression(false)
.enable_deterministic_tags(true)
.build_date(
time::SystemTime::UNIX_EPOCH
.checked_add(time::Duration::from_secs(1_456_304_492))
.unwrap(),
);
if let Some(ref ps) = self.unstables.paper_size {
engine.paper_spec(ps.clone());
}
engine.process(&mut launcher, &xdvname, &pdfname).unwrap();
}
tex_res
};
// Check that outputs match expectations.
let files = mem.files.borrow();
let mut expect = Expected::new()
.res(self.expected_result, res)
.file(expected_log.collection(&files));
if expect_xdv {
expect =
expect.file(ExpectedFile::read_with_extension(&mut p, "xdv").collection(&files));
}
if self.check_synctex {
expect = expect.file(
ExpectedFile::read_with_extension_rooted_gz(&mut p, "synctex.gz")
.collection(&files),
);
}
if self.check_pdf {
expect =
expect.file(ExpectedFile::read_with_extension(&mut p, "pdf").collection(&files));
}
expect.finish();
}
}
// Keep these alphabetized.
#[test]
fn a4paper() {
let unstables = UnstableOptions {
paper_size: Some(String::from("a4")),
..Default::default()
};
TestCase::new("a4paper")
.with_unstables(unstables)
.check_pdf(true)
.go()
}
#[test]
fn file_encoding() {
// Need to do this here since we call test_path unusually early.
util::set_test_root();
TestCase::new("file_encoding.tex")
.with_fs(&test_path(&["tex-outputs"]))
.expect(Ok(TexOutcome::Warnings))
.go()
}
// Works around an issue where old (~2.7) Harfbuzz lays out glyphs differently.
// Remove this once all external Harfbuzz versions don't exhibit the glyph-swapping behavior.
#[cfg(not(any(feature = "external-harfbuzz", target_arch = "x86")))]
#[test]
fn utf8_chars() {
TestCase::new("utf8_chars")
.expect(Ok(TexOutcome::Warnings))
.go();
}
/// An issue triggered by a bug in how the I/O subsystem reported file offsets
/// after an ungetc() call.
#[test]
fn issue393_ungetc() {
TestCase::new("issue393_ungetc")
.expect(Ok(TexOutcome::Warnings))
.go()
}
#[test]
fn md5_of_hello() {
TestCase::new("md5_of_hello").check_pdf(true).go()
}
#[test]
fn negative_roman_numeral() {
TestCase::new("negative_roman_numeral").go()
}
#[test]
fn otf_basic() {
TestCase::new("otf_basic")
.expect(Ok(TexOutcome::Warnings))
.go()
}
#[test]
fn graphite_basic() {
TestCase::new("graphite_basic").go()
}
#[test]
fn prim_creationdate() {
TestCase::new("prim_creationdate").go()
}
#[test]
fn prim_filedump() {
TestCase::new("prim_filedump").go()
}
#[test]
fn prim_filemoddate() {
// Git doesn't preserve mtimes, so manually force the mtime of the input
// file to something repeatable.
util::set_test_root();
let path = test_path(&["tex-outputs", "prim_filemoddate.tex"]);
let t = filetime::FileTime::from_unix_time(1_603_835_905, 0);
filetime::set_file_mtime(path, t).expect("failed to set input file mtime");
TestCase::new("prim_filemoddate").go()
}
#[test]
fn prim_filesize() {
TestCase::new("prim_filesize").go()
}
#[test]
fn shell_escape() {
let unstables = tectonic::unstable_opts::UnstableOptions {
shell_escape: true,
..Default::default()
};
TestCase::new("shell_escape")
.with_unstables(unstables)
.check_pdf(true)
.go()
}
#[test]
fn no_shell_escape() {
let unstables = tectonic::unstable_opts::UnstableOptions {
shell_escape: false,
..Default::default()
};
TestCase::new("no_shell_escape")
.with_unstables(unstables)
.check_pdf(true)
.go()
}
#[test]
fn tex_logo() {
TestCase::new("tex_logo").go()
}
#[test]
fn pdfoutput() {
TestCase::new("pdfoutput").go()
}
#[test]
fn pipe_input() {
TestCase::new("pipe_input")
.expect_msg("failed to open input file \"|pipeproblems\"")
.go()
}
#[test]
fn png_formats() {
TestCase::new("png_formats").check_pdf(true).go()
}
#[test]
fn redbox_png() {
TestCase::new("redbox_png").check_pdf(true).go()
}
#[test]
fn synctex() {
TestCase::new("synctex").check_synctex(true).go()
}
#[test]
fn unicode_file_name() {
TestCase::new("hallöchen 🐨 welt 🌍.tex")
.expect(Ok(TexOutcome::Warnings))
.go()
}
#[test]
fn tectoniccodatokens_errinside() {
TestCase::new("tectoniccodatokens_errinside")
.expect_msg("halted on potentially-recoverable error as specified")
.go()
}
#[test]
fn tectoniccodatokens_noend() {
TestCase::new("tectoniccodatokens_noend")
.expect_msg("*** (job aborted, no legal \\end found)")
.go()
}
#[test]
fn tectoniccodatokens_ok() {
TestCase::new("tectoniccodatokens_ok").go()
}
#[test]
fn the_letter_a() {
TestCase::new("the_letter_a").check_pdf(true).go()
}
#[test]
fn xetex_g_builtins() {
TestCase::new("xetex_g_builtins").check_pdf(true).go()
}
#[test]
fn xetex_ot_builtins() {
TestCase::new("xetex_ot_builtins").check_pdf(true).go()
}