366 lines
9.5 KiB
Rust
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()
|
|
}
|