rumtk_core/
lib.rs

1/*
2 * rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 * This toolkit aims to be reliable, simple, performant, and standards compliant.
4 * Copyright (C) 2024  Luis M. Santos, M.D.
5 * Copyright (C) 2025  MedicalMasses L.L.C.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22//#![feature(unboxed_closures)]
23//#![feature(inherent_associated_types)]
24#![feature(type_alias_impl_trait)]
25#![feature(unboxed_closures)]
26#![feature(buf_read_has_data_left)]
27#![feature(mapped_lock_guards)]
28
29pub mod cache;
30pub mod cli;
31pub mod core;
32pub mod json;
33pub mod log;
34pub mod maths;
35pub mod net;
36pub mod queue;
37pub mod scripting;
38pub mod search;
39pub mod strings;
40pub mod threading;
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use crate::cache::RUMCache;
46    use crate::search::rumtk_search::*;
47    use crate::strings::{
48        rumtk_format, RUMArrayConversions, RUMString, RUMStringConversions, StringUtils,
49    };
50    use compact_str::CompactString;
51    use serde::{Deserialize, Deserializer, Serialize, Serializer};
52    use serde_json::to_string;
53    use std::sync::Arc;
54    use tokio::sync::RwLock;
55
56    #[test]
57    fn test_is_escaped_str() {
58        let input = "\r\n\'\\\"";
59        let expected = false;
60        let result = strings::is_escaped_str(input);
61        println!("Input: {} Expected: {} Got: {}", input, expected, result);
62        assert_eq!(
63            expected, result,
64            "Incorrect detection of unescaped string as escaped!!"
65        );
66        println!("Passed!")
67    }
68
69    #[test]
70    fn test_escaping_control() {
71        let input = "\r\n\'\"\\";
72        let expected = "\\r\\n\\'\\\"\\\\";
73        let result = strings::escape(input);
74        println!(
75            "Input: {} Expected: {} Got: {}",
76            input,
77            expected,
78            result.as_str()
79        );
80        assert_eq!(expected, result, "Incorrect string escaping!");
81        println!("Passed!")
82    }
83
84    #[test]
85    fn test_escaping_unicode() {
86        let input = "❤";
87        let expected = "\\u2764";
88        let result = strings::escape(input);
89        println!(
90            "Input: {} Expected: {} Got: {}",
91            input,
92            expected,
93            result.as_str()
94        );
95        assert_eq!(expected, result, "Incorrect string escaping!");
96        println!("Passed!")
97    }
98
99    #[test]
100    fn test_unescaping_unicode() {
101        let input = "❤";
102        let escaped = strings::escape(input);
103        let expected = "❤";
104        let result = RUMString::from_utf8(strings::unescape(escaped.as_str()).unwrap()).unwrap();
105        println!(
106            "Input: {} Expected: {} Got: {}",
107            input,
108            expected,
109            result.as_str()
110        );
111        assert_eq!(expected, result.as_str(), "Incorrect string unescaping!");
112        println!("Passed!")
113    }
114
115    #[test]
116    fn test_unescaping_string() {
117        let input = "I \\u2764 my wife!";
118        let expected = "I ❤ my wife!";
119        let result = strings::unescape_string(input).unwrap();
120        println!(
121            "Input: {} Expected: {} Got: {}",
122            input,
123            expected,
124            result.as_str()
125        );
126        assert_eq!(expected, result.as_str(), "Incorrect string unescaping!");
127        println!("Passed!")
128    }
129
130    #[test]
131    fn test_is_escaped_string() {
132        let input = "I \\u2764 my wife!";
133        let expected = true;
134        let result = strings::is_escaped_str(input);
135        println!("Input: {} Expected: {} Got: {}", input, expected, result);
136        assert_eq!(
137            expected, result,
138            "Escaped string detected as unescaped string!"
139        );
140        println!("Passed!")
141    }
142
143    #[test]
144    fn test_is_unescaped_string() {
145        let input = "I ❤ my wife!";
146        let expected = false;
147        let result = strings::is_escaped_str(input);
148        println!("Input: {} Expected: {} Got: {}", input, expected, result);
149        assert_eq!(
150            expected, result,
151            "Unescaped string detected as escaped string!"
152        );
153        println!("Passed!")
154    }
155
156    #[test]
157    fn test_unique_string() {
158        let input = "I❤mywife!";
159        assert!(input.is_unique(), "String was not detected as unique.");
160    }
161
162    #[test]
163    fn test_non_unique_string() {
164        let input = "I❤❤mywife!";
165        assert!(!input.is_unique(), "String was detected as unique.");
166    }
167
168    #[test]
169    fn test_escaping_string() {
170        let input = "I ❤ my wife!";
171        let expected = "I \\u2764 my wife!";
172        let result = strings::escape(input);
173        println!(
174            "Input: {} Expected: {} Got: {}",
175            input,
176            expected,
177            result.as_str()
178        );
179        assert_eq!(expected, result.as_str(), "Incorrect string escaping!");
180        println!("Passed!")
181    }
182
183    #[test]
184    fn test_autodecode_utf8() {
185        let input = "I ❤ my wife!";
186        let result = strings::try_decode(input.as_bytes());
187        println!(
188            "Input: {} Expected: {} Got: {}",
189            input,
190            input,
191            result.as_str()
192        );
193        assert_eq!(input, result, "Incorrect string decoding!");
194        println!("Passed!")
195    }
196
197    #[test]
198    fn test_autodecode_other() {
199        //TODO: Need an example of other encoding texts.
200        let input = "I ❤ my wife!";
201        let result = input;
202        println!("Input: {} Expected: {} Got: {}", input, input, result);
203        assert_eq!(input, result, "Incorrect string decoding!");
204        println!("Passed!")
205    }
206
207    #[test]
208    fn test_decode() {
209        let input = "I ❤ my wife!";
210        let result = strings::try_decode_with(input.as_bytes(), "utf-8");
211        println!(
212            "Input: {} Expected: {} Got: {}",
213            input,
214            input,
215            result.as_str()
216        );
217        assert_eq!(input, result, "Incorrect string decoding!");
218        println!("Passed!")
219    }
220
221    #[test]
222    fn test_rumcache_insertion() {
223        let mut cache: RUMCache<&str, CompactString> = RUMCache::with_capacity(5);
224        cache.insert("❤", CompactString::from("I ❤ my wife!"));
225        println!("Contents: {:#?}", &cache);
226        assert_eq!(cache.len(), 1, "Incorrect number of items in cache!");
227        println!("Passed!")
228    }
229
230    #[test]
231    fn test_search_string_letters() {
232        let input = "Hello World!";
233        let expr = r"\w";
234        let result = string_search(input, expr, "");
235        let expected: RUMString = RUMString::from("HelloWorld");
236        println!(
237            "Input: {:?} Expected: {:?} Got: {:?}",
238            input, expected, result
239        );
240        assert_eq!(expected, result, "String search results mismatch");
241        println!("Passed!")
242    }
243
244    #[test]
245    fn test_search_string_words() {
246        let input = "Hello World!";
247        let expr = r"\w+";
248        let result = string_search(input, expr, " ");
249        let expected: RUMString = RUMString::from("Hello World");
250        println!(
251            "Input: {:?} Expected: {:?} Got: {:?}",
252            input, expected, result
253        );
254        assert_eq!(expected, result, "String search results mismatch");
255        println!("Passed!")
256    }
257
258    #[test]
259    fn test_search_string_named_groups() {
260        let input = "Hello World!";
261        let expr = r"(?<hello>\w{5}) (?<world>\w{5})";
262        let result = string_search_named_captures(input, expr, "");
263        let expected: RUMString = RUMString::from("World");
264        println!(
265            "Input: {:?} Expected: {:?} Got: {:?}",
266            input, expected, result
267        );
268        assert_eq!(expected, result["world"], "String search results mismatch");
269        println!("Passed!")
270    }
271
272    #[test]
273    fn test_search_string_all_groups() {
274        let input = "Hello World!";
275        let expr = r"(?<hello>\w{5}) (?<world>\w{5})";
276        let result = string_search_all_captures(input, expr, "");
277        let expected: Vec<&str> = vec!["Hello", "World"];
278        println!(
279            "Input: {:?} Expected: {:?} Got: {:?}",
280            input, expected, result
281        );
282        assert_eq!(expected, result, "String search results mismatch");
283        println!("Passed!")
284    }
285
286    ///////////////////////////////////Threading Tests/////////////////////////////////////////////////
287    #[test]
288    fn test_default_num_threads() {
289        use num_cpus;
290        let threads = threading::threading_functions::get_default_system_thread_count();
291        assert_eq!(
292            threads >= num_cpus::get(),
293            true,
294            "Default thread count is incorrect! We got {}, but expected {}!",
295            threads,
296            num_cpus::get()
297        );
298    }
299
300    #[test]
301    fn test_execute_job() {
302        let rt = rumtk_init_threads!();
303        let expected = vec![1, 2, 3];
304        let task_processor = async |args: &SafeTaskArgs<i32>| -> TaskResult<i32> {
305            let owned_args = Arc::clone(args);
306            let lock_future = owned_args.read();
307            let locked_args = lock_future.await;
308            let mut results = TaskItems::<i32>::with_capacity(locked_args.len());
309            print!("Contents: ");
310            for arg in locked_args.iter() {
311                results.push(arg.clone());
312                println!("{} ", &arg);
313            }
314            Ok(results)
315        };
316        let locked_args = RwLock::new(expected.clone());
317        let task_args = SafeTaskArgs::<i32>::new(locked_args);
318        let task_result = rumtk_wait_on_task!(rt, task_processor, &task_args);
319        let result = task_result.unwrap();
320        assert_eq!(&result, &expected, "{}", rumtk_format!("Task processing returned a different result than expected! Expected {:?} \nResults {:?}", &expected, &result));
321    }
322
323    #[test]
324    fn test_execute_job_macros() {
325        let rt = rumtk_init_threads!();
326        let expected = vec![1, 2, 3];
327        let task_processor = async |args: &SafeTaskArgs<i32>| -> TaskResult<i32> {
328            let owned_args = Arc::clone(args);
329            let lock_future = owned_args.read();
330            let locked_args = lock_future.await;
331            let mut results = TaskItems::<i32>::with_capacity(locked_args.len());
332            print!("Contents: ");
333            for arg in locked_args.iter() {
334                results.push(arg.clone());
335                println!("{} ", &arg);
336            }
337            Ok(results)
338        };
339        let task_args = rumtk_create_task_args!(1, 2, 3);
340        let task_result = rumtk_wait_on_task!(rt, task_processor, &task_args);
341        let result = task_result.unwrap();
342        assert_eq!(&result, &expected, "{}", rumtk_format!("Task processing returned a different result than expected! Expected {:?} \nResults {:?}", &expected, &result));
343    }
344
345    #[test]
346    fn test_execute_job_macros_one_line() {
347        let rt = rumtk_init_threads!();
348        let expected = vec![1, 2, 3];
349        let result = rumtk_exec_task!(
350            async |args: &SafeTaskArgs<i32>| -> TaskResult<i32> {
351                let owned_args = Arc::clone(args);
352                let lock_future = owned_args.read();
353                let locked_args = lock_future.await;
354                let mut results = TaskItems::<i32>::with_capacity(locked_args.len());
355                print!("Contents: ");
356                for arg in locked_args.iter() {
357                    results.push(arg.clone());
358                    println!("{} ", &arg);
359                }
360                Ok(results)
361            },
362            vec![1, 2, 3]
363        )
364        .unwrap();
365        assert_eq!(&result, &expected, "{}", rumtk_format!("Task processing returned a different result than expected! Expected {:?} \nResults {:?}", &expected, &result));
366    }
367
368    #[test]
369    fn test_clamp_index_positive_index() {
370        let values = vec![1, 2, 3, 4];
371        let given_index = 3isize;
372        let max_size = values.len() as isize;
373        let index = clamp_index(&given_index, &max_size).unwrap();
374        assert_eq!(
375            index, 3,
376            "Index mismatch! Requested index {} but got {}",
377            &given_index, &index
378        );
379        assert_eq!(
380            values[index], 4,
381            "Value mismatch! Expected {} but got {}",
382            &values[3], &values[index]
383        );
384    }
385
386    #[test]
387    fn test_clamp_index_reverse_index() {
388        let values = vec![1, 2, 3, 4];
389        let given_index = -1isize;
390        let max_size = values.len() as isize;
391        let index = clamp_index(&given_index, &max_size).unwrap();
392        assert_eq!(
393            index, 4,
394            "Index mismatch! Requested index {} but got {}",
395            &given_index, &index
396        );
397        assert_eq!(
398            values[index - 1],
399            4,
400            "Value mismatch! Expected {} but got {}",
401            &values[3],
402            &values[index]
403        );
404    }
405
406    ///////////////////////////////////Queue Tests/////////////////////////////////////////////////
407    use crate::cli::cli_utils::print_license_notice;
408    use crate::core::clamp_index;
409    use crate::net::tcp::LOCALHOST;
410    use crate::threading::thread_primitives::{SafeTaskArgs, TaskItems, TaskResult};
411    use crate::threading::threading_functions::sleep;
412    use queue::queue::*;
413
414    #[test]
415    fn test_queue_data() {
416        let expected = vec![
417            RUMString::from("Hello"),
418            RUMString::from("World!"),
419            RUMString::from("Overcast"),
420            RUMString::from("and"),
421            RUMString::from("Sad"),
422        ];
423        let mut queue = TaskQueue::<RUMString>::new(&5).unwrap();
424        let locked_args = RwLock::new(expected.clone());
425        let task_args = SafeTaskArgs::<RUMString>::new(locked_args);
426        let processor = rumtk_create_task!(
427            async |args: &SafeTaskArgs<RUMString>| -> TaskResult<RUMString> {
428                let owned_args = Arc::clone(args);
429                let lock_future = owned_args.read();
430                let locked_args = lock_future.await;
431                let mut results = TaskItems::<RUMString>::with_capacity(locked_args.len());
432                print!("Contents: ");
433                for arg in locked_args.iter() {
434                    print!("{} ", &arg);
435                    results.push(RUMString::new(arg));
436                }
437                Ok(results)
438            },
439            task_args
440        );
441        queue.add_task::<_>(processor);
442        let results = queue.wait();
443        let mut result_data = Vec::<RUMString>::with_capacity(5);
444        for r in results {
445            for v in r.unwrap().iter() {
446                result_data.push(v.clone());
447            }
448        }
449        assert_eq!(result_data, expected, "Results do not match expected!");
450    }
451
452    ///////////////////////////////////Net Tests/////////////////////////////////////////////////
453    #[test]
454    fn test_server_start() {
455        let mut server = match rumtk_create_server!("localhost", 0) {
456            Ok(server) => server,
457            Err(e) => panic!("Failed to create server because {}", e),
458        };
459        match server.start(false) {
460            Ok(_) => (),
461            Err(e) => panic!("Failed to start server because {}", e),
462        }
463    }
464
465    #[test]
466    fn test_server_send() {
467        let msg = RUMString::from("Hello World!");
468        let mut server = match rumtk_create_server!(LOCALHOST, 0, 1) {
469            Ok(server) => server,
470            Err(e) => panic!("Failed to create server because {}", e),
471        };
472        match server.start(false) {
473            Ok(_) => (),
474            Err(e) => panic!("Failed to start server because {}", e),
475        };
476        let address_info = server.get_address_info().unwrap();
477        let (ip, port) = rumtk_get_ip_port!(address_info);
478        println!("Sleeping");
479        rumtk_sleep!(1);
480        let mut client = match rumtk_connect!(port) {
481            Ok(client) => client,
482            Err(e) => panic!("Failed to create server because {}", e),
483        };
484        let client_id = client.get_address().unwrap();
485        rumtk_sleep!(1);
486        match server.send(&client_id, &msg.to_raw()) {
487            Ok(_) => (),
488            Err(e) => panic!("Server failed to send message because {}", e),
489        };
490        rumtk_sleep!(1);
491        let received_message = client.receive().unwrap();
492        assert_eq!(
493            &msg.to_raw(),
494            &received_message,
495            "{}",
496            rumtk_format!(
497                "Received message does not match sent message by server {:?}",
498                &received_message
499            )
500        );
501    }
502
503    #[test]
504    fn test_server_receive() {
505        let msg = RUMString::from("Hello World!");
506        let mut server = match rumtk_create_server!(LOCALHOST, 0) {
507            Ok(server) => server,
508            Err(e) => panic!("Failed to create server because {}", e),
509        };
510        match server.start(false) {
511            Ok(_) => (),
512            Err(e) => panic!("Failed to start server because {}", e),
513        };
514        let address_info = server.get_address_info().unwrap();
515        let (ip, port) = rumtk_get_ip_port!(address_info);
516        println!("Sleeping");
517        rumtk_sleep!(1);
518        let mut client = match rumtk_connect!(port) {
519            Ok(client) => client,
520            Err(e) => panic!("Failed to create server because {}", e),
521        };
522        match client.send(&msg.to_raw()) {
523            Ok(_) => (),
524            Err(e) => panic!("Failed to send message because {}", e),
525        };
526        rumtk_sleep!(1);
527        let client_id = client.get_address().expect("Failed to get client id");
528        let incoming_message = server.receive(&client_id).unwrap().to_rumstring();
529        println!("Received message => {:?}", &incoming_message);
530        assert_eq!(&incoming_message, msg, "Received message corruption!");
531    }
532
533    #[test]
534    fn test_server_get_clients() {
535        let mut server = match rumtk_create_server!(LOCALHOST, 0) {
536            Ok(server) => server,
537            Err(e) => panic!("Failed to create server because {}", e),
538        };
539        match server.start(false) {
540            Ok(_) => (),
541            Err(e) => panic!("Failed to start server because {}", e),
542        };
543        let address_info = server.get_address_info().unwrap();
544        let (ip, port) = rumtk_get_ip_port!(address_info);
545        println!("Sleeping");
546        rumtk_sleep!(1);
547        let mut client = match rumtk_connect!(port) {
548            Ok(client) => client,
549            Err(e) => panic!("Failed to create server because {}", e),
550        };
551        rumtk_sleep!(1);
552        let expected_client_id = client.get_address().expect("Failed to get client id");
553        let clients = server.get_client_ids();
554        let incoming_client_id = clients.get(0).expect("Expected client to have connected!");
555        println!("Connected client id => {}", &incoming_client_id);
556        assert_eq!(
557            &incoming_client_id, &expected_client_id,
558            "Connected client does not match the connecting client! Client id => {}",
559            &incoming_client_id
560        );
561    }
562
563    #[test]
564    fn test_server_stop() {
565        let msg = RUMString::from("Hello World!");
566        let mut server = match rumtk_create_server!("localhost", 0) {
567            Ok(server) => server,
568            Err(e) => panic!("Failed to create server because {}", e),
569        };
570        match server.start(false) {
571            Ok(_) => (),
572            Err(e) => panic!("Failed to start server because {}", e),
573        };
574        println!("Sleeping");
575        rumtk_sleep!(1);
576        match server.stop() {
577            Ok(_) => (),
578            Err(e) => panic!("Failed to stop server because {}", e),
579        };
580    }
581
582    #[test]
583    fn test_server_get_address_info() {
584        let msg = RUMString::from("Hello World!");
585        let mut server = match rumtk_create_server!("localhost", 0) {
586            Ok(server) => server,
587            Err(e) => panic!("Failed to create server because {}", e),
588        };
589        match server.start(false) {
590            Ok(_) => (),
591            Err(e) => panic!("Failed to start server because {}", e),
592        };
593        println!("Sleeping");
594        rumtk_sleep!(1);
595        match server.get_address_info() {
596            Some(addr) => println!("Server address info => {}", addr),
597            None => panic!("No address. Perhaps the server was never initialized?"),
598        };
599    }
600
601    #[test]
602    fn test_client_send() {
603        let msg = RUMString::from("Hello World!");
604        let mut server = match rumtk_create_server!(LOCALHOST, 0) {
605            Ok(server) => server,
606            Err(e) => panic!("Failed to create server because {}", e),
607        };
608        match server.start(false) {
609            Ok(_) => (),
610            Err(e) => panic!("Failed to start server because {}", e),
611        };
612        let address_info = server.get_address_info().unwrap();
613        let (ip, port) = rumtk_get_ip_port!(address_info);
614        println!("Sleeping");
615        rumtk_sleep!(1);
616        let mut client = match rumtk_connect!(port) {
617            Ok(client) => client,
618            Err(e) => panic!("Failed to create server because {}", e),
619        };
620        rumtk_sleep!(2);
621        match client.send(&msg.to_raw()) {
622            Ok(_) => (),
623            Err(e) => panic!("Failed to send message because {}", e),
624        };
625        rumtk_sleep!(1);
626        let clients = server.get_client_ids();
627        let incoming_client_id = clients.first().expect("Expected client to have connected!");
628        let mut received_message = server.receive(incoming_client_id).unwrap();
629        if received_message.is_empty() {
630            rumtk_sleep!(1);
631            received_message = server.receive(incoming_client_id).unwrap();
632        }
633        assert_eq!(
634            &msg.to_raw(),
635            &received_message,
636            "{}",
637            rumtk_format!(
638                "Received message does not match sent message by client {:?}",
639                &received_message
640            )
641        );
642    }
643
644    ////////////////////////////JSON Tests/////////////////////////////////
645
646    #[test]
647    fn test_serialize_json() {
648        #[derive(Serialize)]
649        struct MyStruct {
650            hello: RUMString,
651        }
652
653        let hw = MyStruct {
654            hello: RUMString::from("World"),
655        };
656        let hw_str = rumtk_serialize!(&hw, true).unwrap();
657
658        assert!(
659            !hw_str.is_empty(),
660            "Empty JSON string generated from the test struct!"
661        );
662    }
663
664    #[test]
665    fn test_deserialize_serde_json() {
666        use serde::{Deserialize, Deserializer, Serialize, Serializer};
667        use serde_json::{from_str, to_string};
668
669        #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
670        struct MyStruct {
671            hello: RUMString,
672        }
673
674        let hw = MyStruct {
675            hello: RUMString::from("World"),
676        };
677        let hw_str = to_string(&hw).unwrap();
678        let new_hw: MyStruct = from_str(&hw_str).unwrap();
679
680        assert_eq!(
681            new_hw, hw,
682            "Deserialized JSON does not match the expected value!"
683        );
684    }
685
686    #[test]
687    fn test_deserialize_json() {
688        #[derive(Serialize, Deserialize, PartialEq)]
689        struct MyStruct {
690            hello: RUMString,
691        }
692
693        let hw = MyStruct {
694            hello: RUMString::from("World"),
695        };
696        let hw_str = rumtk_serialize!(&hw, true).unwrap();
697        let new_hw: MyStruct = rumtk_deserialize!(&hw_str).unwrap();
698
699        assert!(
700            new_hw == hw,
701            "Deserialized JSON does not match the expected value!"
702        );
703    }
704
705    /*
706    #[test]
707    fn test_escape_unescape_json() {
708        #[derive(Serialize, Deserialize, PartialEq)]
709        struct MyStruct {
710            hello: RUMString,
711        }
712
713        let hw = MyStruct {
714            hello: RUMString::from("World"),
715        };
716
717        let hw_str = rumtk_serialize!(&hw, true).unwrap();
718        let hw_escaped_str = strings::basic_escape(&hw_str);
719        println!("Escaped => {}", hw_escaped_str);
720
721        let hw_unescaped_str = strings::unescape_string(&hw_escaped_str).unwrap();
722        println!("Unescaped => {}", hw_unescaped_str);
723        assert_eq!(
724            hw_str.to_rumstring(),
725            hw_unescaped_str.to_rumstring(),
726            "Unescaped serialized JSON mismatch!"
727        );
728
729        let new_hw: MyStruct = rumtk_deserialize!(&hw_unescaped_str).unwrap();
730
731        assert!(
732            new_hw == hw,
733            "Deserialized JSON does not match the expected value!"
734        );
735    }
736    */
737
738    ////////////////////////////CLI Tests/////////////////////////////////
739
740    #[test]
741    fn test_print_license_notice() {
742        print_license_notice("RUMTK", "2025", &vec!["Luis M. Santos, M.D."]);
743    }
744
745    //////////////////////////////////////////////////////////////////////////////////////////////
746}