libxml_rs/xml/globals/mod.rs
1//! Global state management (§57, §85 Phase 1).
2//!
3//! Manages all library-wide global state:
4//!
5//! - Parser defaults (validity checking, entity substitution, blanks, etc.)
6//! - Generic and structured error callbacks
7//! - Catalog defaults
8//! - Memory hooks (connects to allocator.rs)
9//! - Thread-local state for errors
10//! - Initialization/cleanup reference counting
11//!
12//! # UPSTREAM-PARITY
13//!
14//! libxml2 has many global variables that control default parser behavior.
15//! These are exposed as public ABI symbols that applications can read/write
16//! directly. We use atomic operations for thread safety while maintaining
17//! the same observable semantics.
18//!
19//! # Thread safety
20//!
21//! All global state uses atomic operations or parking_lot locks.
22//! Error state is thread-local via `thread_local!`.
23//!
24//! # Phase 1 status
25//!
26//! Complete — all global state management is implemented.
27//! Future phases may add historical version-specific behavior.
28
29use core::cell::RefCell;
30use core::ffi::c_void;
31use core::ptr;
32use core::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, Ordering};
33use std::os::raw::c_int;
34
35use crate::abi::allocator;
36use crate::abi::callbacks::{xmlGenericErrorFunc, xmlStructuredErrorFunc};
37use crate::abi::structs::_xmlError;
38use crate::abi::types::xmlErrorLevel::XML_ERR_NONE;
39use crate::abi::types::*;
40use crate::abi::versioning;
41
42/// Serializes the exported error-handler slot pairs
43/// (`xmlGenericError`/`xmlGenericErrorContext` and
44/// `xmlStructuredError`/`xmlStructuredErrorContext`). Upstream's globals are
45/// bare racy `static mut` slots; the candidate keeps the C-visible symbols
46/// but makes internal set/get atomic as a (handler, ctx) pair so readers
47/// never observe a new handler with an old context (or vice versa). C
48/// consumers that touch the symbols directly keep upstream's documented
49/// racy semantics.
50static ERROR_HANDLER_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
51
52/// Serializes the error-handler tests that mutate the shared handler slots
53/// (11.1-X regression court wiring): `test_error_callbacks_*` (globals) and
54/// `test_structured_error_callback` (errors) must not run concurrently, or
55/// `test_error_callbacks_default_handlers` observes another test's
56/// temporarily-installed structured handler.
57#[cfg(test)]
58pub(crate) static ERROR_HANDLER_TEST_LOCK: parking_lot::Mutex<()> = parking_lot::Mutex::new(());
59
60// ═══════════════════════════════════════════════════════════════════════════════
61// Initialization Reference Counting
62// ═══════════════════════════════════════════════════════════════════════════════
63
64/// Reference count for xmlInitParser / xmlCleanupParser.
65static INIT_REF_COUNT: AtomicI32 = AtomicI32::new(0);
66
67/// Whether threading has been initialized.
68static THREADS_INITIALIZED: AtomicBool = AtomicBool::new(false);
69
70// ═══════════════════════════════════════════════════════════════════════════════
71// Parser Defaults
72// ═══════════════════════════════════════════════════════════════════════════════
73//
74// These are the global parser default variables exposed by libxml2's ABI.
75// Applications can read and write them directly to change default behavior.
76//
77// Upstream declarations (from parser.h / parserInternals.h):
78//
79// ```c
80// extern int xmlDoValidityCheckingDefaultValue;
81// extern int xmlDoWarningsDefaultValue;
82// extern int xmlIndentTreeOutput;
83// extern int xmlKeepBlanksDefaultValue;
84// extern int xmlLoadExtDtdDefaultValue;
85// extern int xmlPedanticParserDefaultValue;
86// ═══════════════════════════════════════════════════════════════════════════════
87// Parser Defaults
88// ═══════════════════════════════════════════════════════════════════════════════
89//
90// The defaults live in the EXPORTED C globals (src/abi/data_globals.rs) so
91// that downstream C code reading/writing them directly (the upstream
92// contract) observes and controls the same state the parser uses. The
93// accessors below are the safe-Rust view of those statics.
94
95// ═══════════════════════════════════════════════════════════════════════════════
96// Error Callback Globals
97// ═══════════════════════════════════════════════════════════════════════════════
98// Also exported as C globals (xmlGenericError/xmlGenericErrorContext,
99// xmlStructuredError/xmlStructuredErrorContext).
100
101// ═══════════════════════════════════════════════════════════════════════════════
102// Catalog Defaults
103// ═══════════════════════════════════════════════════════════════════════════════
104
105/// Catalog default allow value.
106/// 0 = strict, 1 = allow, 2 = allow document, -1 = none.
107/// Internal only (upstream keeps catalog state inside xmlCatalogSetDefaults;
108/// there is no public C global for it).
109static CATALOG_DEFAULTS: AtomicI32 = AtomicI32::new(0);
110
111// ═══════════════════════════════════════════════════════════════════════════════
112// Thread-Local Error State
113// ═══════════════════════════════════════════════════════════════════════════════
114
115thread_local! {
116 /// Last error for this thread.
117 static LAST_ERROR: RefCell<Option<_xmlError>> = const { RefCell::new(None) };
118}
119
120// ═══════════════════════════════════════════════════════════════════════════════
121// Public Accessors — Parser Defaults
122// ═══════════════════════════════════════════════════════════════════════════════
123
124/// Get the default validity checking value.
125pub fn get_validity_checking_default() -> c_int {
126 // SAFETY: reading an exported int global; matches upstream semantics.
127 unsafe { crate::abi::data_globals::xmlDoValidityCheckingDefaultValue }
128}
129
130/// Set the default validity checking value.
131pub fn set_validity_checking_default(val: c_int) {
132 // SAFETY: writing an exported int global; matches upstream semantics.
133 unsafe {
134 crate::abi::data_globals::xmlDoValidityCheckingDefaultValue = val;
135 }
136}
137
138/// Get the default warnings value.
139pub fn get_do_warnings_default() -> c_int {
140 // SAFETY: reading an exported int global (upstream xmlGetWarningsDefaultValue).
141 unsafe { crate::abi::data_globals::xmlGetWarningsDefaultValue }
142}
143
144/// Set the default warnings value.
145pub fn set_do_warnings_default(val: c_int) {
146 // SAFETY: writing an exported int global.
147 unsafe {
148 crate::abi::data_globals::xmlGetWarningsDefaultValue = val;
149 }
150}
151
152/// Get the indent tree output default.
153pub fn get_indent_tree_output() -> c_int {
154 // SAFETY: reading an exported int global (upstream xmlIndentTreeOutput).
155 unsafe { crate::abi::data_globals::xmlIndentTreeOutput }
156}
157
158/// Set the indent tree output default.
159pub fn set_indent_tree_output(val: c_int) {
160 // SAFETY: writing an exported int global.
161 unsafe {
162 crate::abi::data_globals::xmlIndentTreeOutput = val;
163 }
164}
165
166/// Get the keep blanks default value.
167pub fn get_keep_blanks_default() -> c_int {
168 // SAFETY: reading an exported int global (upstream xmlKeepBlanksDefaultValue).
169 unsafe { crate::abi::data_globals::xmlKeepBlanksDefaultValue }
170}
171
172/// Set the keep blanks default value.
173pub fn set_keep_blanks_default(val: c_int) {
174 // SAFETY: writing an exported int global.
175 unsafe {
176 crate::abi::data_globals::xmlKeepBlanksDefaultValue = val;
177 }
178}
179
180/// Get the load external DTD default value.
181pub fn get_load_ext_dtd_default() -> c_int {
182 // SAFETY: reading an exported int global (upstream xmlLoadExtDtdDefaultValue).
183 unsafe { crate::abi::data_globals::xmlLoadExtDtdDefaultValue }
184}
185
186/// Set the load external DTD default value.
187pub fn set_load_ext_dtd_default(val: c_int) {
188 // SAFETY: writing an exported int global.
189 unsafe {
190 crate::abi::data_globals::xmlLoadExtDtdDefaultValue = val;
191 }
192}
193
194/// Get the pedantic parser default.
195pub fn get_pedantic_parser_default() -> c_int {
196 // SAFETY: reading an exported int global (upstream xmlPedanticParserDefaultValue).
197 unsafe { crate::abi::data_globals::xmlPedanticParserDefaultValue }
198}
199
200/// Set the pedantic parser default.
201pub fn set_pedantic_parser_default(val: c_int) {
202 // SAFETY: writing an exported int global.
203 unsafe {
204 crate::abi::data_globals::xmlPedanticParserDefaultValue = val;
205 }
206}
207
208/// Get the substitute entities default.
209pub fn get_substitute_entities_default() -> c_int {
210 // SAFETY: reading an exported int global (upstream xmlSubstituteEntitiesDefaultValue).
211 unsafe { crate::abi::data_globals::xmlSubstituteEntitiesDefaultValue }
212}
213
214/// Set the substitute entities default.
215pub fn set_substitute_entities_default(val: c_int) {
216 // SAFETY: writing an exported int global.
217 unsafe {
218 crate::abi::data_globals::xmlSubstituteEntitiesDefaultValue = val;
219 }
220}
221
222/// Get the save no empty tags default.
223pub fn get_save_no_empty_tags() -> c_int {
224 // SAFETY: reading an exported int global (upstream xmlSaveNoEmptyTags).
225 unsafe { crate::abi::data_globals::xmlSaveNoEmptyTags }
226}
227
228/// Set the save no empty tags default.
229pub fn set_save_no_empty_tags(val: c_int) {
230 // SAFETY: writing an exported int global.
231 unsafe {
232 crate::abi::data_globals::xmlSaveNoEmptyTags = val;
233 }
234}
235
236/// Get the get warnings default.
237pub fn get_get_warnings_default() -> c_int {
238 // SAFETY: reading an exported int global (upstream xmlGetWarningsDefaultValue).
239 unsafe { crate::abi::data_globals::xmlGetWarningsDefaultValue }
240}
241
242/// Set the get warnings default.
243pub fn set_get_warnings_default(val: c_int) {
244 // SAFETY: writing an exported int global.
245 unsafe {
246 crate::abi::data_globals::xmlGetWarningsDefaultValue = val;
247 }
248}
249
250// ═══════════════════════════════════════════════════════════════════════════════
251// Public Accessors — Error Callbacks
252// ═══════════════════════════════════════════════════════════════════════════════
253
254/// Set the generic error handler.
255///
256/// # SAFETY
257///
258/// - `handler` must be a valid function pointer or NULL (to reset to default).
259/// - If non-NULL, the handler may be called at any time with `ctx`.
260pub unsafe fn set_generic_error_func(ctx: *mut c_void, handler: Option<xmlGenericErrorFunc>) {
261 // SAFETY: writing the exported C globals xmlGenericErrorContext /
262 // xmlGenericError; matches upstream xmlSetGenericErrorFunc (NULL resets
263 // to the built-in default stderr printer, error.c). The (ctx, func)
264 // pair is written atomically under ERROR_HANDLER_LOCK.
265 let resolved = match handler {
266 Some(h) => Some(h),
267 None => unsafe { crate::abi::data_globals::default_generic_error_func() },
268 };
269 let _guard = ERROR_HANDLER_LOCK.lock();
270 unsafe {
271 crate::abi::data_globals::xmlGenericErrorContext = ctx;
272 crate::abi::data_globals::xmlGenericError = resolved;
273 }
274}
275
276/// Get the generic error handler context.
277pub fn get_generic_error_ctx() -> *mut c_void {
278 let _guard = ERROR_HANDLER_LOCK.lock();
279 // SAFETY: reading the exported C global xmlGenericErrorContext.
280 unsafe { crate::abi::data_globals::xmlGenericErrorContext }
281}
282
283/// Get the generic error handler function pointer.
284pub fn get_generic_error_func() -> Option<xmlGenericErrorFunc> {
285 let _guard = ERROR_HANDLER_LOCK.lock();
286 // SAFETY: reading the exported C global xmlGenericError.
287 unsafe { crate::abi::data_globals::xmlGenericError }
288}
289
290/// Read the generic error (func, ctx) pair atomically.
291///
292/// The closure runs after the lock is released, so a handler installed
293/// by the callback cannot deadlock.
294pub fn with_generic_error<R>(f: impl FnOnce(Option<xmlGenericErrorFunc>, *mut c_void) -> R) -> R {
295 let (h, c) = {
296 let _guard = ERROR_HANDLER_LOCK.lock();
297 (
298 unsafe { crate::abi::data_globals::xmlGenericError },
299 unsafe { crate::abi::data_globals::xmlGenericErrorContext },
300 )
301 };
302 f(h, c)
303}
304
305/// Set the structured error handler.
306///
307/// # SAFETY
308///
309/// - `handler` must be a valid function pointer or NULL.
310pub unsafe fn set_structured_error_func(ctx: *mut c_void, handler: Option<xmlStructuredErrorFunc>) {
311 // SAFETY: writing the exported C globals xmlStructuredErrorContext /
312 // xmlStructuredError; matches upstream xmlSetStructuredErrorFunc. The
313 // (ctx, func) pair is written atomically under ERROR_HANDLER_LOCK.
314 let _guard = ERROR_HANDLER_LOCK.lock();
315 unsafe {
316 crate::abi::data_globals::xmlStructuredErrorContext = ctx;
317 crate::abi::data_globals::xmlStructuredError = handler;
318 }
319}
320
321/// Get the structured error handler context.
322pub fn get_structured_error_ctx() -> *mut c_void {
323 let _guard = ERROR_HANDLER_LOCK.lock();
324 // SAFETY: reading the exported C global xmlStructuredErrorContext.
325 unsafe { crate::abi::data_globals::xmlStructuredErrorContext }
326}
327
328/// Get the structured error handler function pointer.
329pub fn get_structured_error_func() -> Option<xmlStructuredErrorFunc> {
330 let _guard = ERROR_HANDLER_LOCK.lock();
331 // SAFETY: reading the exported C global xmlStructuredError.
332 unsafe { crate::abi::data_globals::xmlStructuredError }
333}
334
335/// Read the structured error (func, ctx) pair atomically.
336///
337/// The closure runs after the lock is released, so a handler installed by
338/// the callback (or an error raised from inside the handler) cannot
339/// deadlock on ERROR_HANDLER_LOCK.
340pub fn with_structured_error<R>(
341 f: impl FnOnce(Option<xmlStructuredErrorFunc>, *mut c_void) -> R,
342) -> R {
343 let (h, c) = {
344 let _guard = ERROR_HANDLER_LOCK.lock();
345 (
346 unsafe { crate::abi::data_globals::xmlStructuredError },
347 unsafe { crate::abi::data_globals::xmlStructuredErrorContext },
348 )
349 };
350 f(h, c)
351}
352
353// ═══════════════════════════════════════════════════════════════════════════════
354// Public Accessors — Thread-Local Error State
355// ═══════════════════════════════════════════════════════════════════════════════
356
357/// Get the last error for this thread.
358///
359/// Returns a mutable pointer to the last error, or NULL if no error occurred.
360/// The returned pointer is valid until the next libxml2 call in this thread.
361pub fn get_last_error() -> *mut _xmlError {
362 LAST_ERROR.with(|last| {
363 let mut last = last.borrow_mut();
364 last.as_mut()
365 .map_or(ptr::null_mut(), |e| e as *mut _xmlError)
366 })
367}
368
369/// Get a reference to the last error (for structured error callback).
370pub fn with_last_error<F, R>(f: F) -> R
371where
372 F: FnOnce(Option<&_xmlError>) -> R,
373{
374 LAST_ERROR.with(|last| f(last.borrow().as_ref()))
375}
376
377/// Set the last error for this thread.
378pub fn set_last_error(err: _xmlError) {
379 // UPSTREAM-PARITY: mirror the error into the exported C global
380 // `xmlLastError` (data-ABI, residual R-000135). Upstream keeps a single
381 // global; the candidate keeps a thread-local truth and a deep-copied
382 // mirror so C consumers see the most recent error with upstream
383 // lifetime semantics (mirror strings are owned by the mirror and freed
384 // on reset, matching xmlResetError).
385 //
386 // SAFETY: sync_xml_last_error only reads `err` and writes the global
387 // mirror with freshly owned copies; the thread-local slot takes
388 // ownership of `err` itself.
389 unsafe { crate::abi::data_globals::sync_xml_last_error(&err) };
390 LAST_ERROR.with(|last| {
391 let mut last = last.borrow_mut();
392 // Free the previous slot's owned strings (upstream xmlResetError).
393 if let Some(prev) = last.as_ref() {
394 free_error_strings(prev);
395 }
396 *last = Some(err);
397 });
398}
399
400/// Free the owned string fields of a stored error (upstream xmlResetError:
401/// message/file/str1/str2/str3 are xmlMalloc'd copies).
402fn free_error_strings(err: &_xmlError) {
403 use crate::abi::allocator::xmlFreeImpl;
404 unsafe {
405 if !err.message.is_null() {
406 xmlFreeImpl(err.message as *mut core::ffi::c_void);
407 }
408 if !err.file.is_null() {
409 xmlFreeImpl(err.file as *mut core::ffi::c_void);
410 }
411 if !err.str1.is_null() {
412 xmlFreeImpl(err.str1 as *mut core::ffi::c_void);
413 }
414 if !err.str2.is_null() {
415 xmlFreeImpl(err.str2 as *mut core::ffi::c_void);
416 }
417 if !err.str3.is_null() {
418 xmlFreeImpl(err.str3 as *mut core::ffi::c_void);
419 }
420 }
421}
422
423/// Reset the last error for this thread.
424pub fn reset_last_error() {
425 LAST_ERROR.with(|last| {
426 let mut last = last.borrow_mut();
427 if let Some(prev) = last.as_ref() {
428 free_error_strings(prev);
429 }
430 *last = None;
431 });
432 // SAFETY: frees the mirror's owned strings and zeroes the global.
433 unsafe { crate::abi::data_globals::reset_xml_last_error() };
434}
435
436// ═══════════════════════════════════════════════════════════════════════════════
437// Public Accessors — Catalog Defaults
438// ═══════════════════════════════════════════════════════════════════════════════
439
440/// Get the catalog default allow value.
441pub fn get_catalog_defaults() -> c_int {
442 CATALOG_DEFAULTS.load(Ordering::Relaxed)
443}
444
445/// Set the catalog default allow value.
446pub fn set_catalog_defaults(val: c_int) {
447 CATALOG_DEFAULTS.store(val, Ordering::Relaxed);
448}
449
450// ═══════════════════════════════════════════════════════════════════════════════
451// Initialization / Cleanup
452// ═══════════════════════════════════════════════════════════════════════════════
453
454/// Initialize the parser library.
455///
456/// Must be called before any other libxml2 functions.
457/// Safe to call multiple times (reference-counted in modern libxml2).
458///
459/// # UPSTREAM-PARITY
460///
461/// In modern libxml2 (2.12+), `xmlInitParser` is reference-counted.
462/// The first call initializes all subsystems; subsequent calls
463/// increment a counter. `xmlCleanupParser` decrements the counter
464/// and only performs cleanup when it reaches zero.
465///
466/// # SAFETY
467///
468/// Not fully thread-safe during the first call; callers should
469/// call `xmlInitParser` before creating threads.
470pub unsafe fn init_parser() {
471 let prev = INIT_REF_COUNT.fetch_add(1, Ordering::AcqRel);
472 if prev == 0 {
473 // First initialization — initialize all subsystems.
474 // 1. Initialize memory subsystem.
475 allocator::xmlInitMemory();
476
477 // 2. Mark the library as initialized.
478 versioning::set_initialized();
479
480 // 3. Initialize encoding handlers.
481 crate::xml::encoding::init_encodings();
482
483 // 4. Initialize thread support.
484 init_threads();
485 }
486}
487
488/// Clean up the parser library.
489///
490/// Should be called when the library is no longer needed.
491/// Only performs actual cleanup when the reference count reaches zero.
492///
493/// # SAFETY
494///
495/// Must not be called while other libxml2 functions are executing
496/// in any thread.
497pub unsafe fn cleanup_parser() {
498 let prev = INIT_REF_COUNT.fetch_sub(1, Ordering::AcqRel);
499 if prev <= 1 {
500 // Last cleanup — clean up all subsystems.
501 // 1. Clean up catalog.
502 crate::xml::catalog::cleanup();
503
504 // 2. Clean up encoding handlers.
505 crate::xml::encoding::cleanup_encodings();
506
507 // 3. Clean up memory.
508 allocator::xmlCleanupMemory();
509
510 // 3. Reset initialization state.
511 // Note: we do NOT reset the initialized flag in case
512 // some code checks it after cleanup. This matches
513 // upstream behavior where xmlCleanupParser is best-effort.
514 }
515}
516
517/// Initialize threading support.
518///
519/// # UPSTREAM-PARITY
520///
521/// In modern libxml2, threading is initialized automatically
522/// by `xmlInitParser`. This function exists for backward
523/// compatibility.
524///
525/// Returns 0 on success.
526pub fn init_threads() -> c_int {
527 if !THREADS_INITIALIZED.swap(true, Ordering::Release) {
528 // First initialization — no-op in Rust since we use
529 // standard thread-safe primitives.
530 // In libxml2 this would set up pthread mutexes.
531 }
532 0
533}
534
535/// Clean up threading support.
536pub fn cleanup_threads() {
537 THREADS_INITIALIZED.store(false, Ordering::Release);
538}
539
540/// Check whether threads have been initialized.
541pub fn threads_initialized() -> bool {
542 THREADS_INITIALIZED.load(Ordering::Acquire)
543}
544
545/// Get the current initialization reference count.
546pub fn init_ref_count() -> c_int {
547 INIT_REF_COUNT.load(Ordering::Relaxed)
548}
549
550// ═══════════════════════════════════════════════════════════════════════════════
551// Tests
552// ═══════════════════════════════════════════════════════════════════════════════
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557
558 #[test]
559 fn test_parser_defaults_initial_values() {
560 assert_eq!(get_validity_checking_default(), 0);
561 assert_eq!(get_do_warnings_default(), 1);
562 // UPSTREAM-PARITY (globals.c 2.15): xmlIndentTreeOutputThrDef = 1.
563 assert_eq!(get_indent_tree_output(), 1);
564 assert_eq!(get_keep_blanks_default(), 1);
565 assert_eq!(get_load_ext_dtd_default(), 0);
566 assert_eq!(get_pedantic_parser_default(), 0);
567 assert_eq!(get_substitute_entities_default(), 0);
568 assert_eq!(get_save_no_empty_tags(), 0);
569 assert_eq!(get_get_warnings_default(), 1);
570 }
571
572 #[test]
573 fn test_parser_defaults_set_and_get() {
574 set_validity_checking_default(1);
575 assert_eq!(get_validity_checking_default(), 1);
576 set_validity_checking_default(0);
577 assert_eq!(get_validity_checking_default(), 0);
578
579 set_keep_blanks_default(0);
580 assert_eq!(get_keep_blanks_default(), 0);
581 set_keep_blanks_default(1);
582 assert_eq!(get_keep_blanks_default(), 1);
583
584 set_substitute_entities_default(1);
585 assert_eq!(get_substitute_entities_default(), 1);
586 set_substitute_entities_default(0);
587 assert_eq!(get_substitute_entities_default(), 0);
588 }
589
590 #[test]
591 fn test_init_cleanup_ref_count() {
592 // Reset for test
593 unsafe {
594 init_parser();
595 assert_eq!(init_ref_count(), 1);
596
597 init_parser();
598 assert_eq!(init_ref_count(), 2);
599
600 cleanup_parser();
601 assert!(init_ref_count() == 1 || init_ref_count() == 0);
602
603 // Final cleanup
604 cleanup_parser();
605 }
606 }
607
608 #[test]
609 fn test_error_callbacks_default_handlers() {
610 // UPSTREAM-PARITY (error.c): xmlGenericError defaults to the built-in
611 // stderr printer (never NULL); xmlStructuredError defaults to NULL.
612 // Serialized against the other handler-mutating tests (11.1-X): the
613 // slots are shared global state.
614 let _guard = ERROR_HANDLER_TEST_LOCK.lock();
615 #[cfg(target_arch = "x86_64")]
616 assert!(get_generic_error_func().is_some());
617 assert!(get_structured_error_func().is_none());
618 }
619
620 #[test]
621 fn test_error_callbacks_set_and_get() {
622 let _guard = ERROR_HANDLER_TEST_LOCK.lock();
623 unsafe {
624 unsafe extern "C" fn dummy_handler(_ctx: *mut c_void, _msg: *const core::ffi::c_char) {}
625 let dummy_func: xmlGenericErrorFunc = dummy_handler;
626 let dummy_ctx: *mut c_void = &mut 0 as *mut i32 as *mut c_void;
627
628 set_generic_error_func(dummy_ctx, Some(dummy_func));
629 assert!(get_generic_error_func().is_some());
630 assert_eq!(get_generic_error_ctx(), dummy_ctx);
631
632 // UPSTREAM-PARITY (xmlSetGenericErrorFunc): NULL resets to the
633 // built-in default printer, it does not unset the handler.
634 set_generic_error_func(ptr::null_mut(), None);
635 #[cfg(target_arch = "x86_64")]
636 assert!(get_generic_error_func().is_some());
637 assert_eq!(get_generic_error_ctx(), ptr::null_mut());
638 }
639 }
640
641 #[test]
642 fn test_last_error_thread_local() {
643 assert!(get_last_error().is_null());
644
645 let err = _xmlError {
646 domain: XML_FROM_PARSER,
647 code: XML_ERR_OK as c_int,
648 message: ptr::null_mut(),
649 level: XML_ERR_NONE as c_int,
650 file: ptr::null_mut(),
651 line: 0,
652 str1: ptr::null_mut(),
653 str2: ptr::null_mut(),
654 str3: ptr::null_mut(),
655 int1: 0,
656 int2: 0,
657 ctxt: ptr::null_mut(),
658 node: ptr::null_mut(),
659 };
660 set_last_error(err);
661 assert!(!get_last_error().is_null());
662 unsafe {
663 assert_eq!((*get_last_error()).domain, XML_FROM_PARSER);
664 }
665
666 reset_last_error();
667 assert!(get_last_error().is_null());
668 }
669
670 #[test]
671 fn test_catalog_defaults() {
672 // Save original value (may have been set by init_parser in other tests)
673 let orig = get_catalog_defaults();
674 set_catalog_defaults(1);
675 assert_eq!(get_catalog_defaults(), 1);
676 set_catalog_defaults(0);
677 assert_eq!(get_catalog_defaults(), 0);
678 // Restore
679 set_catalog_defaults(orig);
680 }
681}