#![ cfg( feature = "enabled" ) ]
use include_md::include_md_section;
use std ::path ::PathBuf;
#[ test ]
fn top_level_section_includes_nested_subsections()
{
let section = include_md_section!( "tests/fixture/multi_section.md", "# Introduction" );
let expected = concat!(
"# Introduction\n",
"\n",
"Welcome to the test fixture.\n",
"\n",
"## Overview\n",
"\n",
"This subsection is under Introduction.\n",
"\n",
"### Detail\n",
"\n",
"A nested subsection.\n",
"\n",
);
assert_eq!( section, expected, "# Introduction must include all nested subsections" );
}
#[ test ]
fn subsection_stops_at_next_equal_or_higher_level_heading()
{
let section = include_md_section!( "tests/fixture/multi_section.md", "## Overview" );
let expected = concat!(
"## Overview\n",
"\n",
"This subsection is under Introduction.\n",
"\n",
"### Detail\n",
"\n",
"A nested subsection.\n",
"\n",
);
assert_eq!( section, expected, "## Overview must end before # Usage (higher level)" );
}
#[ test ]
fn h2_section_stops_at_sibling_h2()
{
let section = include_md_section!( "tests/fixture/multi_section.md", "## Installation" );
let expected = concat!(
"## Installation\n",
"\n",
"Run cargo add.\n",
"\n",
);
assert_eq!( section, expected, "## Installation must stop before ## Configuration" );
}
#[ test ]
fn first_occurrence_wins_for_duplicate_heading()
{
let section = include_md_section!( "tests/fixture/multi_section.md", "# Notes" );
let expected = concat!(
"# Notes\n",
"\n",
"Final section with no subsections.\n",
"\n",
);
assert_eq!( section, expected, "first # Notes must be returned, not the second" );
assert!
(
!section.contains( "second Notes section" ),
"second # Notes occurrence must not appear in result",
);
}
#[ test ]
fn section_with_no_subsections_returns_own_content_only()
{
let section = include_md_section!( "tests/fixture/multi_section.md", "## Configuration" );
let expected = concat!(
"## Configuration\n",
"\n",
"Set options here.\n",
"\n",
);
assert_eq!( section, expected, "## Configuration must stop before # Notes" );
}
#[ test ]
fn code_block_heading_not_a_boundary()
{
let section = include_md_section!( "tests/fixture/edge_cases.md", "## H2 With Code Block" );
let expected = concat!(
"## H2 With Code Block\n",
"\n",
"Content before code block.\n",
"\n",
"```rust\n",
"## this looks like H2 but is code\n",
"```\n",
"\n",
"Content after code block.\n",
"\n",
);
assert_eq!(
section,
expected,
"heading inside backtick code block must not terminate section extraction (BUG-005)",
);
}
#[ test ]
fn tilde_fence_heading_not_a_boundary()
{
let section = include_md_section!( "tests/fixture/edge_cases.md", "## H2 With Tilde Fence" );
let expected = concat!(
"## H2 With Tilde Fence\n",
"\n",
"Content before tilde.\n",
"\n",
"~~~python\n",
"## tilde fence heading\n",
"~~~\n",
"\n",
"Content after tilde.\n",
"\n",
);
assert_eq!(
section,
expected,
"heading inside tilde code block must not terminate section extraction (BUG-005)",
);
}
#[ test ]
fn empty_section_body_returns_heading_and_blank()
{
let section = include_md_section!( "tests/fixture/edge_cases.md", "# Empty Section" );
assert_eq!(
section,
"# Empty Section\n\n",
"section with no body must return heading plus the blank line before the next heading",
);
}
#[ test ]
fn atx_no_space_not_a_heading_boundary()
{
let section = include_md_section!( "tests/fixture/edge_cases.md", "# ATX No Space Section" );
let expected = concat!(
"# ATX No Space Section\n",
"\n",
"##NotAHeading here.\n",
"\n",
"Normal text.\n",
"\n",
);
assert_eq!(
section,
expected,
"##NoSpace (no space after hashes) is not a valid ATX heading and must be treated as content",
);
}
#[ test ]
fn setext_heading_not_a_boundary()
{
let section = include_md_section!( "tests/fixture/edge_cases.md", "# Setext Style Section" );
let expected = concat!(
"# Setext Style Section\n",
"\n",
"Normal text.\n",
"\n",
"Setext Text\n",
"==========\n",
"\n",
"More content after setext.\n",
);
assert_eq!(
section,
expected,
"setext-style heading underlines must not be mistaken for ATX headings or section boundaries",
);
}
fn check_compile_fails( code : &str, features : &[ &str ] ) -> bool
{
let manifest_dir = env!( "CARGO_MANIFEST_DIR" );
let target_root = PathBuf ::from( manifest_dir ).join( "target" ).join( "compile_fail_tests_section_extraction" );
let src_dir = target_root.join( format!( "src_{}", std ::process ::id() ) );
std ::fs ::create_dir_all( src_dir.join( "src" ) ).expect( "create temp src dir" );
let features_str = features
.iter()
.map( | f | format!( "\"{f}\"" ) )
.collect ::< Vec< _ > >()
.join( ", " );
let cargo_toml = format!(
"[package]\n\
name = \"compile_fail_check\"\n\
version = \"0.1.0\"\n\
edition = \"2021\"\n\
\n\
[dependencies]\n\
include_md = {{ path = \"{manifest_dir}\", default-features = false, features = [{features_str}] }}\n",
);
std ::fs ::write( src_dir.join( "Cargo.toml" ), &cargo_toml ).expect( "write temp Cargo.toml" );
std ::fs ::write( src_dir.join( "src/main.rs" ), code ).expect( "write temp main.rs" );
let cargo = std ::env ::var( "CARGO" ).unwrap_or_else( | _ | "cargo".to_owned() );
let output = std ::process ::Command ::new( &cargo )
.args
( [
"check",
"--manifest-path",
&src_dir.join( "Cargo.toml" ).to_string_lossy(),
] )
.env( "CARGO_TARGET_DIR", target_root.join( "build" ) )
.output()
.expect( "cargo check invocation failed" );
let _ = std ::fs ::remove_dir_all( &src_dir );
!output.status.success()
}
#[ test ]
fn missing_file_is_compile_error()
{
assert!(
check_compile_fails
(
"fn main() { let _ = include_md::include_md_section!( \"does_not_exist.md\", \"# X\" ); }",
&[ "enabled" ],
),
"include_md_section! must not compile when the file does not exist",
);
}
#[ test ]
fn heading_not_found_is_compile_error()
{
let manifest_dir = env!( "CARGO_MANIFEST_DIR" );
let abs_fixture = format!( "{manifest_dir}/tests/fixture/multi_section.md" );
let code = format!(
"fn main() {{ let _ = include_md::include_md_section!( \"{abs_fixture}\", \"## DoesNotExist__XYZ\" ); }}"
);
assert!(
check_compile_fails( &code, &[ "enabled" ] ),
"include_md_section! must not compile when the heading is not found",
);
}
#[ test ]
fn wrong_case_heading_is_compile_error()
{
let manifest_dir = env!( "CARGO_MANIFEST_DIR" );
let abs_fixture = format!( "{manifest_dir}/tests/fixture/multi_section.md" );
let code = format!(
"fn main() {{ let _ = include_md::include_md_section!( \"{abs_fixture}\", \"# introduction\" ); }}"
);
assert!(
check_compile_fails( &code, &[ "enabled" ] ),
"include_md_section! must not compile when heading case does not match",
);
}
#[ test ]
fn no_args_is_compile_error()
{
assert!(
check_compile_fails
(
"fn main() { let _ = include_md::include_md_section!(); }",
&[ "enabled" ],
),
"include_md_section! must not compile with no arguments",
);
}
#[ test ]
fn one_arg_is_compile_error()
{
assert!(
check_compile_fails
(
"fn main() { let _ = include_md::include_md_section!( \"fixture/multi_section.md\" ); }",
&[ "enabled" ],
),
"include_md_section! must not compile with only one argument",
);
}
#[ test ]
fn three_args_is_compile_error()
{
assert!(
check_compile_fails
(
"fn main() { let _ = include_md::include_md_section!( \"a.md\", \"# X\", \"extra\" ); }",
&[ "enabled" ],
),
"include_md_section! must not compile with three arguments",
);
}
#[ test ]
fn oversized_file_is_compile_error()
{
use std ::io ::Write;
let tmp_dir = std ::env ::temp_dir().join
(
format!( "include_md_section_oversized_{}", std ::process ::id() )
);
std ::fs ::create_dir_all( &tmp_dir ).expect( "create temp dir" );
let big_file = tmp_dir.join( "big.md" );
{
let mut f = std ::fs ::File ::create( &big_file ).expect( "create big file" );
let chunk = vec![ b'a'; 65_536 ];
let mut written = 0_usize;
while written < 10_000_001
{
let n = ( 10_000_001 - written ).min( chunk.len() );
f.write_all( &chunk[ ..n ] ).expect( "write chunk" );
written += n;
}
}
let abs = big_file.to_string_lossy().into_owned();
let code = format!
(
"fn main() {{ let _ = include_md::include_md_section!( \"{abs}\", \"# Heading\" ); }}"
);
let result = check_compile_fails( &code, &[ "enabled" ] );
let _ = std ::fs ::remove_dir_all( &tmp_dir );
assert!( result, "include_md_section! must not compile when the file exceeds 10 MB" );
}
#[ test ]
fn invalid_utf8_is_compile_error()
{
let tmp_dir = std ::env ::temp_dir().join
(
format!( "include_md_section_utf8_{}", std ::process ::id() )
);
std ::fs ::create_dir_all( &tmp_dir ).expect( "create temp dir" );
let invalid_file = tmp_dir.join( "invalid.bin" );
std ::fs ::write( &invalid_file, [ 0xFF_u8, 0xFE, 0x00 ] ).expect( "write invalid UTF-8" );
let abs = invalid_file.to_string_lossy().into_owned();
let code = format!
(
"fn main() {{ let _ = include_md::include_md_section!( \"{abs}\", \"# Heading\" ); }}"
);
let result = check_compile_fails( &code, &[ "enabled" ] );
let _ = std ::fs ::remove_dir_all( &tmp_dir );
assert!( result, "include_md_section! must not compile with an invalid UTF-8 file" );
}