Merge branch 'test_combinations'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded

This commit is contained in:
Tom Alexander 2023-09-29 16:39:02 -04:00
commit 13697df7ea
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
16 changed files with 230 additions and 53 deletions

View File

@ -19,8 +19,6 @@ fn main() {
// Re-generate the tests if any org-mode files change
println!("cargo:rerun-if-changed=org_mode_samples");
write_header(&mut test_file);
let test_files = WalkDir::new("org_mode_samples")
.into_iter()
.filter(|e| match e {
@ -54,28 +52,15 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
.strip_suffix(".org")
.expect("Should have .org extension")
.replace("/", "_");
let test_name = format!("autogen_{}", test_name);
if let Some(_reason) = is_expect_fail(test_name.as_str()) {
write!(test_file, "#[ignore]\n").unwrap();
}
write!(
test_file,
include_str!("./tests/test_template"),
name = test_name,
path = test.path().display()
)
.unwrap();
}
#[cfg(feature = "compare")]
fn write_header(test_file: &mut File) {
write!(
test_file,
r#"
#[feature(exit_status_error)]
"#
path = test.path().display(),
expect_fail = is_expect_fail(test_name.as_str())
.map(|_| "#[ignore]\n")
.unwrap_or("")
)
.unwrap();
}
@ -83,8 +68,8 @@ fn write_header(test_file: &mut File) {
#[cfg(feature = "compare")]
fn is_expect_fail(name: &str) -> Option<&str> {
match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
_ => None,
}
}

7
notes/test_names.org Normal file
View File

@ -0,0 +1,7 @@
* Autogen tests
The autogen tests are the tests automatically generated to compare the output of Organic vs the upstream Emacs Org-mode parser using the sample documents in the =org_mode_samples= folder. They will have a prefix based on the settings for each test.
- default :: The test is run with the default settings (The upstream Emacs Org-mode determines the default settings)
- la :: Short for "list alphabetic". Enables alphabetic plain lists.
- t# :: Sets the tab-width to # (as in t4 sets the tab-width to 4).
- odd :: Sets the org-odd-levels-only setting to true (meaning "odd" as opposed to "oddeven").

View File

@ -0,0 +1,22 @@
# An ordered list starting at 3
1. [@3] foo
# An ordered list starting at 11
1. [@D] bar
# An ordered list starting at 1 with the contents of "[@kk] baz"
1. [@kk] baz
# A paragraph when org-list-allow-alphabetical is nil
m. lorem
# A paragraph when org-list-allow-alphabetical is nil
m. [@k] ipsum
# An unordered list with :counter set to 3
- [@3] dolar

View File

@ -0,0 +1,30 @@
# Alphabetic lists larger than 26 elements should become numbered. From M-x describe-variable org-list-allow-alphabetical:
#
# > Lists with more than 26 items will fallback to standard numbering.
a. 1
a. 2
a. 3
a. 4
a. 5
a. 6
a. 7
a. 8
a. 9
a. 10
a. 11
a. 12
a. 13
a. 14
a. 15
a. 16
a. 17
a. 18
a. 19
a. 20
a. 21
a. 22
a. 23
a. 24
a. 25
a. 26
a. 27

View File

@ -0,0 +1,5 @@
# "lorem" is prefixed by a tab instead of spaces, so the editor's tab-width value determines whether lorem is a sibling of baz (tab-width 8), a sibling of bar (tab-width < 8), or a child of baz (tab-width > 8).
1. foo
1. bar
1. baz
1. lorem

View File

@ -6,21 +6,31 @@ use crate::compare::parse::emacs_parse_file_org_document;
use crate::compare::parse::get_emacs_version;
use crate::compare::parse::get_org_mode_version;
use crate::compare::sexp::sexp;
use crate::parser::parse;
use crate::context::GlobalSettings;
use crate::context::LocalFileAccessInterface;
use crate::parser::parse_with_settings;
use crate::GlobalSettings;
use crate::LocalFileAccessInterface;
pub fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P,
) -> Result<(), Box<dyn std::error::Error>> {
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default())
}
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
run_compare_on_file_with_settings(org_path, &GlobalSettings::default())
}
pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
org_contents: P,
global_settings: &GlobalSettings,
) -> Result<(), Box<dyn std::error::Error>> {
// TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings.
let org_contents = org_contents.as_ref().replace("\r\n", "\n");
let org_contents = org_contents.as_str();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let rust_parsed = parse(org_contents)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?;
let rust_parsed = parse_with_settings(org_contents, global_settings)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
@ -38,7 +48,10 @@ pub fn run_anonymous_compare<P: AsRef<str>>(
Ok(())
}
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
org_path: P,
global_settings: &GlobalSettings,
) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
@ -53,12 +66,12 @@ pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn st
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
let mut global_settings = global_settings.clone();
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
let org_sexp = emacs_parse_file_org_document(org_path)?;
let org_sexp = emacs_parse_file_org_document(org_path, &global_settings)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);

View File

@ -5,4 +5,6 @@ mod parse;
mod sexp;
mod util;
pub use compare::run_anonymous_compare;
pub use compare::run_anonymous_compare_with_settings;
pub use compare::run_compare_on_file;
pub use compare::run_compare_on_file_with_settings;

View File

@ -1,8 +1,33 @@
use std::path::Path;
use std::process::Command;
pub fn emacs_parse_anonymous_org_document<C>(
use crate::context::HeadlineLevelFilter;
use crate::settings::GlobalSettings;
/// Generate elisp to configure org-mode parsing settings
///
/// Currently only org-list-allow-alphabetical is supported.
fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
// This string concatenation is wildly inefficient but its only called in tests 🤷.
let mut ret = "".to_owned();
if global_settings.list_allow_alphabetical {
ret += "(setq org-list-allow-alphabetical t)\n"
}
if global_settings.tab_width != crate::settings::DEFAULT_TAB_WIDTH {
ret += format!("(setq-default tab-width {})", global_settings.tab_width).as_str();
}
if global_settings.odd_levels_only != HeadlineLevelFilter::default() {
ret += match global_settings.odd_levels_only {
HeadlineLevelFilter::Odd => "(setq org-odd-levels-only t)\n",
HeadlineLevelFilter::OddEven => "(setq org-odd-levels-only nil)\n",
};
}
ret
}
pub(crate) fn emacs_parse_anonymous_org_document<C>(
file_contents: C,
global_settings: &GlobalSettings,
) -> Result<String, Box<dyn std::error::Error>>
where
C: AsRef<str>,
@ -14,10 +39,12 @@ where
(require 'org)
(defun org-table-align () t)
(insert "{escaped_file_contents}")
{global_settings}
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)"#,
escaped_file_contents = escaped_file_contents
escaped_file_contents = escaped_file_contents,
global_settings = global_settings_elisp(global_settings)
);
let mut cmd = Command::new("emacs");
let cmd = cmd
@ -33,7 +60,10 @@ where
Ok(String::from_utf8(org_sexp)?)
}
pub fn emacs_parse_file_org_document<P>(file_path: P) -> Result<String, Box<dyn std::error::Error>>
pub(crate) fn emacs_parse_file_org_document<P>(
file_path: P,
global_settings: &GlobalSettings,
) -> Result<String, Box<dyn std::error::Error>>
where
P: AsRef<Path>,
{
@ -46,9 +76,11 @@ where
r#"(progn
(require 'org)
(defun org-table-align () t)
{global_settings}
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)"#
)"#,
global_settings = global_settings_elisp(global_settings)
);
let mut cmd = Command::new("emacs");
let cmd = cmd

View File

@ -16,7 +16,7 @@ pub struct GlobalSettings<'g, 's> {
/// Set to true to allow for plain lists using single letters as the bullet in the same way that numbers are used.
///
/// Corresponds to the org-list-allow-alphabetical elisp variable.
pub org_list_allow_alphabetical: bool,
pub list_allow_alphabetical: bool,
/// How many spaces a tab should be equal to.
///
@ -29,6 +29,8 @@ pub struct GlobalSettings<'g, 's> {
pub odd_levels_only: HeadlineLevelFilter,
}
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
impl<'g, 's> GlobalSettings<'g, 's> {
fn new() -> GlobalSettings<'g, 's> {
GlobalSettings {
@ -38,9 +40,9 @@ impl<'g, 's> GlobalSettings<'g, 's> {
},
in_progress_todo_keywords: BTreeSet::new(),
complete_todo_keywords: BTreeSet::new(),
org_list_allow_alphabetical: false,
tab_width: 8,
odd_levels_only: HeadlineLevelFilter::OddEven,
list_allow_alphabetical: false,
tab_width: DEFAULT_TAB_WIDTH,
odd_levels_only: HeadlineLevelFilter::default(),
}
}
}
@ -51,8 +53,14 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum HeadlineLevelFilter {
Odd,
OddEven,
}
impl Default for HeadlineLevelFilter {
fn default() -> Self {
HeadlineLevelFilter::OddEven
}
}

View File

@ -26,5 +26,6 @@ pub use file_access_interface::FileAccessInterface;
pub use file_access_interface::LocalFileAccessInterface;
pub use global_settings::GlobalSettings;
pub use global_settings::HeadlineLevelFilter;
pub use global_settings::DEFAULT_TAB_WIDTH;
pub(crate) use list::List;
pub(crate) use parser_with_context::parser_with_context;

View File

@ -11,6 +11,10 @@ mod iter;
pub mod parser;
pub mod types;
pub use context::FileAccessInterface;
pub use context::GlobalSettings;
pub use context::LocalFileAccessInterface;
pub mod settings {
pub use crate::context::FileAccessInterface;
pub use crate::context::GlobalSettings;
pub use crate::context::HeadlineLevelFilter;
pub use crate::context::LocalFileAccessInterface;
pub use crate::context::DEFAULT_TAB_WIDTH;
}

View File

@ -5,8 +5,8 @@ use std::path::Path;
use ::organic::parser::parse;
use organic::parser::parse_with_settings;
use organic::GlobalSettings;
use organic::LocalFileAccessInterface;
use organic::settings::GlobalSettings;
use organic::settings::LocalFileAccessInterface;
#[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry;

View File

@ -11,8 +11,8 @@ use super::OrgSource;
use crate::context::HeadlineLevelFilter;
use crate::error::CustomError;
use crate::error::Res;
use crate::settings::GlobalSettings;
use crate::types::Keyword;
use crate::GlobalSettings;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn scan_for_in_buffer_settings<'s>(

View File

@ -166,12 +166,8 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|(_bullet_type, bull)| Into::<&str>::into(bull) != "*" || indent_level > 0,
)(remaining)?;
let (remaining, _maybe_counter_set) = opt(tuple((
space1,
tag("[@"),
parser_with_context!(counter)(context),
tag("]"),
)))(remaining)?;
let (remaining, _maybe_counter_set) =
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
@ -302,7 +298,7 @@ fn counter<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
if context.get_global_settings().org_list_allow_alphabetical {
if context.get_global_settings().list_allow_alphabetical {
alt((
recognize(one_of(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
@ -314,6 +310,16 @@ fn counter<'b, 'g, 'r, 's>(
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
recognize(one_of(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
)),
digit1,
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_list_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,

View File

@ -1,2 +1,5 @@
#[cfg(feature = "compare")]
#![cfg(feature = "compare")]
#[feature(exit_status_error)]
include!(concat!(env!("OUT_DIR"), "/tests.rs"));

View File

@ -1,7 +1,66 @@
// TODO: Investigate writing a proc macro to make specifying these combinations easier. For example, currently I am only setting 1 setting per test to keep the repetition reasonable when I should be mixing all the different combinations.
{expect_fail}
#[test]
fn {name}() -> Result<(), Box<dyn std::error::Error>> {{
fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
organic::compare::run_anonymous_compare(org_contents.as_str())?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
let mut global_settings = organic::settings::GlobalSettings::default();
global_settings.list_allow_alphabetical = true;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
let mut global_settings = organic::settings::GlobalSettings::default();
global_settings.tab_width = 1;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
let mut global_settings = organic::settings::GlobalSettings::default();
global_settings.tab_width = 16;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
let mut global_settings = organic::settings::GlobalSettings::default();
global_settings.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
Ok(())
}}