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