1use super::parser::Ast;
2use lazy_static::lazy_static;
3use std::cell::RefCell;
4use std::collections::{HashMap, HashSet, VecDeque};
5use std::env;
6use std::fs::{File, OpenOptions};
7use std::io::IsTerminal;
8use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
9use std::process::Stdio;
10use std::rc::Rc;
11use std::sync::{Arc, Mutex};
12use std::time::Instant;
13
14lazy_static! {
15 pub static ref SIGNAL_QUEUE: Arc<Mutex<VecDeque<SignalEvent>>> =
18 Arc::new(Mutex::new(VecDeque::new()));
19}
20
21const MAX_SIGNAL_QUEUE_SIZE: usize = 100;
23
24#[derive(Debug, Clone)]
26pub struct SignalEvent {
27 pub signal_name: String,
29 pub signal_number: i32,
31 pub timestamp: Instant,
33}
34
35impl SignalEvent {
36 pub fn new(signal_name: String, signal_number: i32) -> Self {
37 Self {
38 signal_name,
39 signal_number,
40 timestamp: Instant::now(),
41 }
42 }
43}
44
45#[derive(Debug)]
47pub enum FileDescriptor {
48 File(File),
50 Duplicate(RawFd),
52 Closed,
54}
55
56#[derive(Debug)]
58pub struct FileDescriptorTable {
59 fds: HashMap<i32, FileDescriptor>,
61 saved_fds: HashMap<i32, RawFd>,
63}
64
65impl FileDescriptorTable {
66 pub fn new() -> Self {
68 Self {
69 fds: HashMap::new(),
70 saved_fds: HashMap::new(),
71 }
72 }
73
74 pub fn open_fd(
88 &mut self,
89 fd_num: i32,
90 path: &str,
91 read: bool,
92 write: bool,
93 append: bool,
94 truncate: bool,
95 ) -> Result<(), String> {
96 if !(0..=1024).contains(&fd_num) {
98 return Err(format!("Invalid file descriptor number: {}", fd_num));
99 }
100
101 let file = OpenOptions::new()
103 .read(read)
104 .write(write)
105 .append(append)
106 .truncate(truncate)
107 .create(write || append)
108 .open(path)
109 .map_err(|e| format!("Cannot open {}: {}", path, e))?;
110
111 self.fds.insert(fd_num, FileDescriptor::File(file));
113 Ok(())
114 }
115
116 pub fn duplicate_fd(&mut self, source_fd: i32, target_fd: i32) -> Result<(), String> {
126 if !(0..=1024).contains(&source_fd) {
128 return Err(format!("Invalid source file descriptor: {}", source_fd));
129 }
130 if !(0..=1024).contains(&target_fd) {
131 return Err(format!("Invalid target file descriptor: {}", target_fd));
132 }
133
134 if source_fd == target_fd {
136 return Ok(());
137 }
138
139 let raw_fd = match self.get_raw_fd(source_fd) {
141 Some(fd) => fd,
142 None => {
143 return Err(format!(
144 "File descriptor {} is not open or is closed",
145 source_fd
146 ));
147 }
148 };
149
150 self.fds
152 .insert(target_fd, FileDescriptor::Duplicate(raw_fd));
153 Ok(())
154 }
155
156 pub fn close_fd(&mut self, fd_num: i32) -> Result<(), String> {
165 if !(0..=1024).contains(&fd_num) {
167 return Err(format!("Invalid file descriptor number: {}", fd_num));
168 }
169
170 self.fds.insert(fd_num, FileDescriptor::Closed);
172 Ok(())
173 }
174
175 pub fn save_fd(&mut self, fd_num: i32) -> Result<(), String> {
184 if !(0..=1024).contains(&fd_num) {
186 return Err(format!("Invalid file descriptor number: {}", fd_num));
187 }
188
189 let saved_fd = unsafe {
191 let raw_fd = fd_num as RawFd;
192 libc::dup(raw_fd)
193 };
194
195 if saved_fd < 0 {
196 return Err(format!("Failed to save file descriptor {}", fd_num));
197 }
198
199 self.saved_fds.insert(fd_num, saved_fd);
200 Ok(())
201 }
202
203 pub fn restore_fd(&mut self, fd_num: i32) -> Result<(), String> {
212 if !(0..=1024).contains(&fd_num) {
214 return Err(format!("Invalid file descriptor number: {}", fd_num));
215 }
216
217 if let Some(saved_fd) = self.saved_fds.remove(&fd_num) {
219 unsafe {
221 let result = libc::dup2(saved_fd, fd_num as RawFd);
222 libc::close(saved_fd); if result < 0 {
225 return Err(format!("Failed to restore file descriptor {}", fd_num));
226 }
227 }
228
229 self.fds.remove(&fd_num);
231 }
232
233 Ok(())
234 }
235
236 pub fn save_all_fds(&mut self) -> Result<(), String> {
242 let fd_nums: Vec<i32> = self.fds.keys().copied().collect();
244 for fd_num in fd_nums {
245 self.save_fd(fd_num)?;
246 }
247 Ok(())
248 }
249
250 pub fn restore_all_fds(&mut self) -> Result<(), String> {
256 let fd_nums: Vec<i32> = self.saved_fds.keys().copied().collect();
258 for fd_num in fd_nums {
259 self.restore_fd(fd_num)?;
260 }
261 Ok(())
262 }
263
264 #[allow(dead_code)]
273 pub fn get_stdio(&self, fd_num: i32) -> Option<Stdio> {
274 match self.fds.get(&fd_num) {
275 Some(FileDescriptor::File(file)) => {
276 let raw_fd = file.as_raw_fd();
278 let dup_fd = unsafe { libc::dup(raw_fd) };
279 if dup_fd >= 0 {
280 let file = unsafe { File::from_raw_fd(dup_fd) };
281 Some(Stdio::from(file))
282 } else {
283 None
284 }
285 }
286 Some(FileDescriptor::Duplicate(raw_fd)) => {
287 let dup_fd = unsafe { libc::dup(*raw_fd) };
289 if dup_fd >= 0 {
290 let file = unsafe { File::from_raw_fd(dup_fd) };
291 Some(Stdio::from(file))
292 } else {
293 None
294 }
295 }
296 Some(FileDescriptor::Closed) | None => None,
297 }
298 }
299
300 pub fn get_raw_fd(&self, fd_num: i32) -> Option<RawFd> {
309 match self.fds.get(&fd_num) {
310 Some(FileDescriptor::File(file)) => Some(file.as_raw_fd()),
311 Some(FileDescriptor::Duplicate(raw_fd)) => Some(*raw_fd),
312 Some(FileDescriptor::Closed) => None,
313 None => {
314 if fd_num >= 0 && fd_num <= 2 {
316 Some(fd_num as RawFd)
317 } else {
318 None
319 }
320 }
321 }
322 }
323
324 pub fn is_open(&self, fd_num: i32) -> bool {
333 matches!(
334 self.fds.get(&fd_num),
335 Some(FileDescriptor::File(_)) | Some(FileDescriptor::Duplicate(_))
336 )
337 }
338
339 pub fn is_closed(&self, fd_num: i32) -> bool {
348 matches!(self.fds.get(&fd_num), Some(FileDescriptor::Closed))
349 }
350
351 pub fn clear(&mut self) {
353 self.fds.clear();
354 self.saved_fds.clear();
355 }
356}
357
358impl Default for FileDescriptorTable {
359 fn default() -> Self {
360 Self::new()
361 }
362}
363
364#[derive(Debug, Clone)]
365pub struct ColorScheme {
366 pub prompt: String,
368 pub error: String,
370 pub success: String,
372 pub builtin: String,
374 pub directory: String,
376}
377
378impl Default for ColorScheme {
379 fn default() -> Self {
380 Self {
381 prompt: "\x1b[32m".to_string(), error: "\x1b[31m".to_string(), success: "\x1b[32m".to_string(), builtin: "\x1b[36m".to_string(), directory: "\x1b[34m".to_string(), }
387 }
388}
389
390#[derive(Debug, Clone)]
391pub struct ShellState {
392 pub variables: HashMap<String, String>,
394 pub exported: HashSet<String>,
396 pub last_exit_code: i32,
398 pub shell_pid: u32,
400 pub script_name: String,
402 pub dir_stack: Vec<String>,
404 pub aliases: HashMap<String, String>,
406 pub colors_enabled: bool,
408 pub color_scheme: ColorScheme,
410 pub positional_params: Vec<String>,
412 pub functions: HashMap<String, Ast>,
414 pub local_vars: Vec<HashMap<String, String>>,
416 pub function_depth: usize,
418 pub max_recursion_depth: usize,
420 pub returning: bool,
422 pub return_value: Option<i32>,
424 pub capture_output: Option<Rc<RefCell<Vec<u8>>>>,
426 pub condensed_cwd: bool,
428 pub trap_handlers: Arc<Mutex<HashMap<String, String>>>,
430 pub exit_trap_executed: bool,
432 pub exit_requested: bool,
434 pub exit_code: i32,
436 #[allow(dead_code)]
439 pub pending_signals: bool,
440 pub pending_heredoc_content: Option<String>,
442 pub collecting_heredoc: Option<(String, String, String)>, pub fd_table: Rc<RefCell<FileDescriptorTable>>,
446 pub subshell_depth: usize,
448 pub stdin_override: Option<RawFd>,
450}
451
452impl ShellState {
453 pub fn new() -> Self {
454 let shell_pid = std::process::id();
455
456 let no_color = env::var("NO_COLOR").is_ok();
458
459 let rush_colors = env::var("RUSH_COLORS")
461 .map(|v| v.to_lowercase())
462 .unwrap_or_else(|_| "auto".to_string());
463
464 let colors_enabled = match rush_colors.as_str() {
465 "1" | "true" | "on" | "enable" => !no_color && std::io::stdout().is_terminal(),
466 "0" | "false" | "off" | "disable" => false,
467 "auto" => !no_color && std::io::stdout().is_terminal(),
468 _ => !no_color && std::io::stdout().is_terminal(),
469 };
470
471 let rush_condensed = env::var("RUSH_CONDENSED")
473 .map(|v| v.to_lowercase())
474 .unwrap_or_else(|_| "true".to_string());
475
476 let condensed_cwd = match rush_condensed.as_str() {
477 "1" | "true" | "on" | "enable" => true,
478 "0" | "false" | "off" | "disable" => false,
479 _ => true, };
481
482 Self {
483 variables: HashMap::new(),
484 exported: HashSet::new(),
485 last_exit_code: 0,
486 shell_pid,
487 script_name: "rush".to_string(),
488 dir_stack: Vec::new(),
489 aliases: HashMap::new(),
490 colors_enabled,
491 color_scheme: ColorScheme::default(),
492 positional_params: Vec::new(),
493 functions: HashMap::new(),
494 local_vars: Vec::new(),
495 function_depth: 0,
496 max_recursion_depth: 500, returning: false,
498 return_value: None,
499 capture_output: None,
500 condensed_cwd,
501 trap_handlers: Arc::new(Mutex::new(HashMap::new())),
502 exit_trap_executed: false,
503 exit_requested: false,
504 exit_code: 0,
505 pending_signals: false,
506 pending_heredoc_content: None,
507 collecting_heredoc: None,
508 fd_table: Rc::new(RefCell::new(FileDescriptorTable::new())),
509 subshell_depth: 0,
510 stdin_override: None,
511 }
512 }
513
514 pub fn get_var(&self, name: &str) -> Option<String> {
516 match name {
518 "?" => Some(self.last_exit_code.to_string()),
519 "$" => Some(self.shell_pid.to_string()),
520 "0" => Some(self.script_name.clone()),
521 "*" => {
522 if self.positional_params.is_empty() {
524 Some("".to_string())
525 } else {
526 Some(self.positional_params.join(" "))
527 }
528 }
529 "@" => {
530 if self.positional_params.is_empty() {
532 Some("".to_string())
533 } else {
534 Some(self.positional_params.join(" "))
535 }
536 }
537 "#" => Some(self.positional_params.len().to_string()),
538 _ => {
539 if let Ok(index) = name.parse::<usize>()
541 && index > 0
542 && index <= self.positional_params.len()
543 {
544 return Some(self.positional_params[index - 1].clone());
545 }
546
547 for scope in self.local_vars.iter().rev() {
550 if let Some(value) = scope.get(name) {
551 return Some(value.clone());
552 }
553 }
554
555 if let Some(value) = self.variables.get(name) {
557 Some(value.clone())
558 } else {
559 env::var(name).ok()
561 }
562 }
563 }
564 }
565
566 pub fn set_var(&mut self, name: &str, value: String) {
568 for scope in self.local_vars.iter_mut().rev() {
571 if scope.contains_key(name) {
572 scope.insert(name.to_string(), value);
573 return;
574 }
575 }
576
577 self.variables.insert(name.to_string(), value);
579 }
580
581 pub fn unset_var(&mut self, name: &str) {
583 self.variables.remove(name);
584 self.exported.remove(name);
585 }
586
587 pub fn export_var(&mut self, name: &str) {
589 if self.variables.contains_key(name) {
590 self.exported.insert(name.to_string());
591 }
592 }
593
594 pub fn set_exported_var(&mut self, name: &str, value: String) {
596 self.set_var(name, value);
597 self.export_var(name);
598 }
599
600 pub fn get_env_for_child(&self) -> HashMap<String, String> {
602 let mut child_env = HashMap::new();
603
604 for (key, value) in env::vars() {
606 child_env.insert(key, value);
607 }
608
609 for var_name in &self.exported {
611 if let Some(value) = self.variables.get(var_name) {
612 child_env.insert(var_name.clone(), value.clone());
613 }
614 }
615
616 child_env
617 }
618
619 pub fn set_last_exit_code(&mut self, code: i32) {
621 self.last_exit_code = code;
622 }
623
624 pub fn set_script_name(&mut self, name: &str) {
626 self.script_name = name.to_string();
627 }
628
629 pub fn get_condensed_cwd(&self) -> String {
631 match env::current_dir() {
632 Ok(path) => {
633 let path_str = path.to_string_lossy();
634 let components: Vec<&str> = path_str.split('/').collect();
635 if components.is_empty() || (components.len() == 1 && components[0].is_empty()) {
636 return "/".to_string();
637 }
638 let mut result = String::new();
639 for (i, comp) in components.iter().enumerate() {
640 if comp.is_empty() {
641 continue; }
643 if i == components.len() - 1 {
644 result.push('/');
645 result.push_str(comp);
646 } else {
647 result.push('/');
648 if let Some(first) = comp.chars().next() {
649 result.push(first);
650 }
651 }
652 }
653 if result.is_empty() {
654 "/".to_string()
655 } else {
656 result
657 }
658 }
659 Err(_) => "/?".to_string(), }
661 }
662
663 pub fn get_full_cwd(&self) -> String {
665 match env::current_dir() {
666 Ok(path) => path.to_string_lossy().to_string(),
667 Err(_) => "/?".to_string(), }
669 }
670
671 pub fn get_user_hostname(&self) -> String {
673 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
674
675 if let Ok(hostname) = env::var("HOSTNAME")
677 && !hostname.trim().is_empty()
678 {
679 return format!("{}@{}", user, hostname);
680 }
681
682 let hostname = match std::process::Command::new("hostname").output() {
684 Ok(output) if output.status.success() => {
685 String::from_utf8_lossy(&output.stdout).trim().to_string()
686 }
687 _ => "hostname".to_string(), };
689
690 if hostname != "hostname" {
692 unsafe {
693 env::set_var("HOSTNAME", &hostname);
694 }
695 }
696
697 format!("{}@{}", user, hostname)
698 }
699
700 pub fn get_prompt(&self) -> String {
702 let user = env::var("USER").unwrap_or_else(|_| "user".to_string());
703 let prompt_char = if user == "root" { "#" } else { "$" };
704 let cwd = if self.condensed_cwd {
705 self.get_condensed_cwd()
706 } else {
707 self.get_full_cwd()
708 };
709 format!("{}:{} {} ", self.get_user_hostname(), cwd, prompt_char)
710 }
711
712 pub fn set_alias(&mut self, name: &str, value: String) {
714 self.aliases.insert(name.to_string(), value);
715 }
716
717 pub fn get_alias(&self, name: &str) -> Option<&String> {
719 self.aliases.get(name)
720 }
721
722 pub fn remove_alias(&mut self, name: &str) {
724 self.aliases.remove(name);
725 }
726
727 pub fn get_all_aliases(&self) -> &HashMap<String, String> {
729 &self.aliases
730 }
731
732 pub fn set_positional_params(&mut self, params: Vec<String>) {
734 self.positional_params = params;
735 }
736
737 #[allow(dead_code)]
739 pub fn get_positional_params(&self) -> &[String] {
740 &self.positional_params
741 }
742
743 pub fn shift_positional_params(&mut self, count: usize) {
745 if count > 0 {
746 for _ in 0..count {
747 if !self.positional_params.is_empty() {
748 self.positional_params.remove(0);
749 }
750 }
751 }
752 }
753
754 #[allow(dead_code)]
756 pub fn push_positional_param(&mut self, param: String) {
757 self.positional_params.push(param);
758 }
759
760 pub fn define_function(&mut self, name: String, body: Ast) {
762 self.functions.insert(name, body);
763 }
764
765 pub fn get_function(&self, name: &str) -> Option<&Ast> {
767 self.functions.get(name)
768 }
769
770 #[allow(dead_code)]
772 pub fn remove_function(&mut self, name: &str) {
773 self.functions.remove(name);
774 }
775
776 #[allow(dead_code)]
778 pub fn get_function_names(&self) -> Vec<&String> {
779 self.functions.keys().collect()
780 }
781
782 pub fn push_local_scope(&mut self) {
784 self.local_vars.push(HashMap::new());
785 }
786
787 pub fn pop_local_scope(&mut self) {
789 if !self.local_vars.is_empty() {
790 self.local_vars.pop();
791 }
792 }
793
794 pub fn set_local_var(&mut self, name: &str, value: String) {
796 if let Some(current_scope) = self.local_vars.last_mut() {
797 current_scope.insert(name.to_string(), value);
798 } else {
799 self.set_var(name, value);
801 }
802 }
803
804 pub fn enter_function(&mut self) {
806 self.function_depth += 1;
807 if self.function_depth > self.local_vars.len() {
808 self.push_local_scope();
809 }
810 }
811
812 pub fn exit_function(&mut self) {
814 if self.function_depth > 0 {
815 self.function_depth -= 1;
816 if self.function_depth == self.local_vars.len() - 1 {
817 self.pop_local_scope();
818 }
819 }
820 }
821
822 pub fn set_return(&mut self, value: i32) {
824 self.returning = true;
825 self.return_value = Some(value);
826 }
827
828 pub fn clear_return(&mut self) {
830 self.returning = false;
831 self.return_value = None;
832 }
833
834 pub fn is_returning(&self) -> bool {
836 self.returning
837 }
838
839 pub fn get_return_value(&self) -> Option<i32> {
841 self.return_value
842 }
843
844 pub fn set_trap(&mut self, signal: &str, command: String) {
846 if let Ok(mut handlers) = self.trap_handlers.lock() {
847 handlers.insert(signal.to_uppercase(), command);
848 }
849 }
850
851 pub fn get_trap(&self, signal: &str) -> Option<String> {
853 if let Ok(handlers) = self.trap_handlers.lock() {
854 handlers.get(&signal.to_uppercase()).cloned()
855 } else {
856 None
857 }
858 }
859
860 pub fn remove_trap(&mut self, signal: &str) {
862 if let Ok(mut handlers) = self.trap_handlers.lock() {
863 handlers.remove(&signal.to_uppercase());
864 }
865 }
866
867 pub fn get_all_traps(&self) -> HashMap<String, String> {
869 if let Ok(handlers) = self.trap_handlers.lock() {
870 handlers.clone()
871 } else {
872 HashMap::new()
873 }
874 }
875
876 #[allow(dead_code)]
878 pub fn clear_traps(&mut self) {
879 if let Ok(mut handlers) = self.trap_handlers.lock() {
880 handlers.clear();
881 }
882 }
883}
884
885pub fn enqueue_signal(signal_name: &str, signal_number: i32) {
888 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
889 if queue.len() >= MAX_SIGNAL_QUEUE_SIZE {
891 queue.pop_front();
892 eprintln!("Warning: Signal queue overflow, dropping oldest signal");
893 }
894
895 queue.push_back(SignalEvent::new(signal_name.to_string(), signal_number));
896 }
897}
898
899pub fn process_pending_signals(shell_state: &mut ShellState) {
902 if let Ok(mut queue) = SIGNAL_QUEUE.lock() {
904 while let Some(signal_event) = queue.pop_front() {
906 if let Some(trap_cmd) = shell_state.get_trap(&signal_event.signal_name)
908 && !trap_cmd.is_empty()
909 {
910 if shell_state.colors_enabled {
912 eprintln!(
913 "{}Signal {} (signal {}) received at {:?}\x1b[0m",
914 shell_state.color_scheme.builtin,
915 signal_event.signal_name,
916 signal_event.signal_number,
917 signal_event.timestamp
918 );
919 } else {
920 eprintln!(
921 "Signal {} (signal {}) received at {:?}",
922 signal_event.signal_name,
923 signal_event.signal_number,
924 signal_event.timestamp
925 );
926 }
927
928 crate::executor::execute_trap_handler(&trap_cmd, shell_state);
931 }
932 }
933 }
934}
935
936impl Default for ShellState {
937 fn default() -> Self {
938 Self::new()
939 }
940}
941
942#[cfg(test)]
943mod tests {
944 use super::*;
945 use std::sync::Mutex;
946
947 static FILE_LOCK: Mutex<()> = Mutex::new(());
949
950 #[test]
951 fn test_shell_state_basic() {
952 let mut state = ShellState::new();
953 state.set_var("TEST_VAR", "test_value".to_string());
954 assert_eq!(state.get_var("TEST_VAR"), Some("test_value".to_string()));
955 }
956
957 #[test]
958 fn test_special_variables() {
959 let mut state = ShellState::new();
960 state.set_last_exit_code(42);
961 state.set_script_name("test_script");
962
963 assert_eq!(state.get_var("?"), Some("42".to_string()));
964 assert_eq!(state.get_var("$"), Some(state.shell_pid.to_string()));
965 assert_eq!(state.get_var("0"), Some("test_script".to_string()));
966 }
967
968 #[test]
969 fn test_export_variable() {
970 let mut state = ShellState::new();
971 state.set_var("EXPORT_VAR", "export_value".to_string());
972 state.export_var("EXPORT_VAR");
973
974 let child_env = state.get_env_for_child();
975 assert_eq!(
976 child_env.get("EXPORT_VAR"),
977 Some(&"export_value".to_string())
978 );
979 }
980
981 #[test]
982 fn test_unset_variable() {
983 let mut state = ShellState::new();
984 state.set_var("UNSET_VAR", "value".to_string());
985 state.export_var("UNSET_VAR");
986
987 assert!(state.variables.contains_key("UNSET_VAR"));
988 assert!(state.exported.contains("UNSET_VAR"));
989
990 state.unset_var("UNSET_VAR");
991
992 assert!(!state.variables.contains_key("UNSET_VAR"));
993 assert!(!state.exported.contains("UNSET_VAR"));
994 }
995
996 #[test]
997 fn test_get_user_hostname() {
998 let state = ShellState::new();
999 let user_hostname = state.get_user_hostname();
1000 assert!(user_hostname.contains('@'));
1002 }
1003
1004 #[test]
1005 fn test_get_prompt() {
1006 let state = ShellState::new();
1007 let prompt = state.get_prompt();
1008 assert!(prompt.ends_with(" $ "));
1010 assert!(prompt.contains('@'));
1011 }
1012
1013 #[test]
1014 fn test_positional_parameters() {
1015 let mut state = ShellState::new();
1016 state.set_positional_params(vec![
1017 "arg1".to_string(),
1018 "arg2".to_string(),
1019 "arg3".to_string(),
1020 ]);
1021
1022 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1023 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1024 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1025 assert_eq!(state.get_var("4"), None);
1026 assert_eq!(state.get_var("#"), Some("3".to_string()));
1027 assert_eq!(state.get_var("*"), Some("arg1 arg2 arg3".to_string()));
1028 assert_eq!(state.get_var("@"), Some("arg1 arg2 arg3".to_string()));
1029 }
1030
1031 #[test]
1032 fn test_positional_parameters_empty() {
1033 let mut state = ShellState::new();
1034 state.set_positional_params(vec![]);
1035
1036 assert_eq!(state.get_var("1"), None);
1037 assert_eq!(state.get_var("#"), Some("0".to_string()));
1038 assert_eq!(state.get_var("*"), Some("".to_string()));
1039 assert_eq!(state.get_var("@"), Some("".to_string()));
1040 }
1041
1042 #[test]
1043 fn test_shift_positional_params() {
1044 let mut state = ShellState::new();
1045 state.set_positional_params(vec![
1046 "arg1".to_string(),
1047 "arg2".to_string(),
1048 "arg3".to_string(),
1049 ]);
1050
1051 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1052 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1053 assert_eq!(state.get_var("3"), Some("arg3".to_string()));
1054
1055 state.shift_positional_params(1);
1056
1057 assert_eq!(state.get_var("1"), Some("arg2".to_string()));
1058 assert_eq!(state.get_var("2"), Some("arg3".to_string()));
1059 assert_eq!(state.get_var("3"), None);
1060 assert_eq!(state.get_var("#"), Some("2".to_string()));
1061
1062 state.shift_positional_params(2);
1063
1064 assert_eq!(state.get_var("1"), None);
1065 assert_eq!(state.get_var("#"), Some("0".to_string()));
1066 }
1067
1068 #[test]
1069 fn test_push_positional_param() {
1070 let mut state = ShellState::new();
1071 state.set_positional_params(vec!["arg1".to_string()]);
1072
1073 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1074 assert_eq!(state.get_var("#"), Some("1".to_string()));
1075
1076 state.push_positional_param("arg2".to_string());
1077
1078 assert_eq!(state.get_var("1"), Some("arg1".to_string()));
1079 assert_eq!(state.get_var("2"), Some("arg2".to_string()));
1080 assert_eq!(state.get_var("#"), Some("2".to_string()));
1081 }
1082
1083 #[test]
1084 fn test_local_variable_scoping() {
1085 let mut state = ShellState::new();
1086
1087 state.set_var("global_var", "global_value".to_string());
1089 assert_eq!(
1090 state.get_var("global_var"),
1091 Some("global_value".to_string())
1092 );
1093
1094 state.push_local_scope();
1096
1097 state.set_local_var("global_var", "local_value".to_string());
1099 assert_eq!(state.get_var("global_var"), Some("local_value".to_string()));
1100
1101 state.set_local_var("local_var", "local_only".to_string());
1103 assert_eq!(state.get_var("local_var"), Some("local_only".to_string()));
1104
1105 state.pop_local_scope();
1107
1108 assert_eq!(
1110 state.get_var("global_var"),
1111 Some("global_value".to_string())
1112 );
1113 assert_eq!(state.get_var("local_var"), None);
1114 }
1115
1116 #[test]
1117 fn test_nested_local_scopes() {
1118 let mut state = ShellState::new();
1119
1120 state.set_var("test_var", "global".to_string());
1122
1123 state.push_local_scope();
1125 state.set_local_var("test_var", "level1".to_string());
1126 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1127
1128 state.push_local_scope();
1130 state.set_local_var("test_var", "level2".to_string());
1131 assert_eq!(state.get_var("test_var"), Some("level2".to_string()));
1132
1133 state.pop_local_scope();
1135 assert_eq!(state.get_var("test_var"), Some("level1".to_string()));
1136
1137 state.pop_local_scope();
1139 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1140 }
1141
1142 #[test]
1143 fn test_variable_set_in_local_scope() {
1144 let mut state = ShellState::new();
1145
1146 state.set_var("test_var", "global".to_string());
1148 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1149
1150 state.push_local_scope();
1152 state.set_local_var("test_var", "local".to_string());
1153 assert_eq!(state.get_var("test_var"), Some("local".to_string()));
1154
1155 state.pop_local_scope();
1157 assert_eq!(state.get_var("test_var"), Some("global".to_string()));
1158 }
1159
1160 #[test]
1161 fn test_condensed_cwd_environment_variable() {
1162 let state = ShellState::new();
1164 assert!(state.condensed_cwd);
1165
1166 unsafe {
1168 env::set_var("RUSH_CONDENSED", "true");
1169 }
1170 let state = ShellState::new();
1171 assert!(state.condensed_cwd);
1172
1173 unsafe {
1175 env::set_var("RUSH_CONDENSED", "false");
1176 }
1177 let state = ShellState::new();
1178 assert!(!state.condensed_cwd);
1179
1180 unsafe {
1182 env::remove_var("RUSH_CONDENSED");
1183 }
1184 }
1185
1186 #[test]
1187 fn test_get_full_cwd() {
1188 let state = ShellState::new();
1189 let full_cwd = state.get_full_cwd();
1190 assert!(!full_cwd.is_empty());
1191 assert!(full_cwd.contains('/') || full_cwd.contains('\\'));
1193 }
1194
1195 #[test]
1196 fn test_prompt_with_condensed_setting() {
1197 let mut state = ShellState::new();
1198
1199 assert!(state.condensed_cwd);
1201 let prompt_condensed = state.get_prompt();
1202 assert!(prompt_condensed.contains('@'));
1203
1204 state.condensed_cwd = false;
1206 let prompt_full = state.get_prompt();
1207 assert!(prompt_full.contains('@'));
1208
1209 assert!(prompt_condensed.ends_with("$ ") || prompt_condensed.ends_with("# "));
1211 assert!(prompt_full.ends_with("$ ") || prompt_full.ends_with("# "));
1212 }
1213
1214 #[test]
1217 fn test_fd_table_creation() {
1218 let fd_table = FileDescriptorTable::new();
1219 assert!(!fd_table.is_open(0));
1220 assert!(!fd_table.is_open(1));
1221 assert!(!fd_table.is_open(2));
1222 }
1223
1224 #[test]
1225 fn test_fd_table_open_file() {
1226 let mut fd_table = FileDescriptorTable::new();
1227
1228 let temp_file = "/tmp/rush_test_fd_open.txt";
1230 std::fs::write(temp_file, "test content").unwrap();
1231
1232 let result = fd_table.open_fd(3, temp_file, true, false, false, false);
1234 assert!(result.is_ok());
1235 assert!(fd_table.is_open(3));
1236
1237 let _ = std::fs::remove_file(temp_file);
1239 }
1240
1241 #[test]
1242 fn test_fd_table_open_file_for_writing() {
1243 let mut fd_table = FileDescriptorTable::new();
1244
1245 let temp_file = "/tmp/rush_test_fd_write.txt";
1247
1248 let result = fd_table.open_fd(4, temp_file, false, true, false, true);
1250 assert!(result.is_ok());
1251 assert!(fd_table.is_open(4));
1252
1253 let _ = std::fs::remove_file(temp_file);
1255 }
1256
1257 #[test]
1258 fn test_fd_table_invalid_fd_number() {
1259 let mut fd_table = FileDescriptorTable::new();
1260
1261 let result = fd_table.open_fd(-1, "/tmp/test.txt", true, false, false, false);
1263 assert!(result.is_err());
1264 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1265
1266 let result = fd_table.open_fd(1025, "/tmp/test.txt", true, false, false, false);
1267 assert!(result.is_err());
1268 assert!(result.unwrap_err().contains("Invalid file descriptor"));
1269 }
1270
1271 #[test]
1272 fn test_fd_table_duplicate_fd() {
1273 let mut fd_table = FileDescriptorTable::new();
1274
1275 let temp_file = "/tmp/rush_test_fd_dup.txt";
1277 std::fs::write(temp_file, "test content").unwrap();
1278
1279 fd_table
1281 .open_fd(3, temp_file, true, false, false, false)
1282 .unwrap();
1283 assert!(fd_table.is_open(3));
1284
1285 let result = fd_table.duplicate_fd(3, 4);
1287 assert!(result.is_ok());
1288 assert!(fd_table.is_open(4));
1289
1290 let _ = std::fs::remove_file(temp_file);
1292 }
1293
1294 #[test]
1295 fn test_fd_table_duplicate_to_self() {
1296 let mut fd_table = FileDescriptorTable::new();
1297
1298 let temp_file = "/tmp/rush_test_fd_dup_self.txt";
1300 std::fs::write(temp_file, "test content").unwrap();
1301
1302 fd_table
1304 .open_fd(3, temp_file, true, false, false, false)
1305 .unwrap();
1306
1307 let result = fd_table.duplicate_fd(3, 3);
1309 assert!(result.is_ok());
1310 assert!(fd_table.is_open(3));
1311
1312 let _ = std::fs::remove_file(temp_file);
1314 }
1315
1316 #[test]
1317 fn test_fd_table_duplicate_closed_fd() {
1318 let mut fd_table = FileDescriptorTable::new();
1319
1320 let result = fd_table.duplicate_fd(3, 4);
1322 assert!(result.is_err());
1323 assert!(result.unwrap_err().contains("not open"));
1324 }
1325
1326 #[test]
1327 fn test_fd_table_close_fd() {
1328 let mut fd_table = FileDescriptorTable::new();
1329
1330 let temp_file = "/tmp/rush_test_fd_close.txt";
1332 std::fs::write(temp_file, "test content").unwrap();
1333
1334 fd_table
1336 .open_fd(3, temp_file, true, false, false, false)
1337 .unwrap();
1338 assert!(fd_table.is_open(3));
1339
1340 let result = fd_table.close_fd(3);
1342 assert!(result.is_ok());
1343 assert!(fd_table.is_closed(3));
1344 assert!(!fd_table.is_open(3));
1345
1346 let _ = std::fs::remove_file(temp_file);
1348 }
1349
1350 #[test]
1351 fn test_fd_table_save_and_restore() {
1352 let mut fd_table = FileDescriptorTable::new();
1353
1354 let result = fd_table.save_fd(0);
1356 assert!(result.is_ok());
1357
1358 let result = fd_table.restore_fd(0);
1360 assert!(result.is_ok());
1361 }
1362
1363 #[test]
1364 fn test_fd_table_save_all_and_restore_all() {
1365 let _lock = FILE_LOCK.lock().unwrap();
1366 let mut fd_table = FileDescriptorTable::new();
1367
1368 use std::time::{SystemTime, UNIX_EPOCH};
1370 let timestamp = SystemTime::now()
1371 .duration_since(UNIX_EPOCH)
1372 .unwrap()
1373 .as_nanos();
1374 let temp_file1 = format!("/tmp/rush_test_fd_save1_{}.txt", timestamp);
1375 let temp_file2 = format!("/tmp/rush_test_fd_save2_{}.txt", timestamp);
1376
1377 std::fs::write(&temp_file1, "test content 1").unwrap();
1378 std::fs::write(&temp_file2, "test content 2").unwrap();
1379
1380 let f1 = File::open(&temp_file1).unwrap();
1384 let f2 = File::open(&temp_file2).unwrap();
1385 unsafe {
1386 libc::dup2(f1.as_raw_fd(), 50);
1387 libc::dup2(f2.as_raw_fd(), 51);
1388 }
1389
1390 fd_table
1391 .open_fd(50, &temp_file1, true, false, false, false)
1392 .unwrap();
1393 fd_table
1394 .open_fd(51, &temp_file2, true, false, false, false)
1395 .unwrap();
1396
1397 let result = fd_table.save_all_fds();
1399 assert!(result.is_ok());
1400
1401 let result = fd_table.restore_all_fds();
1403 assert!(result.is_ok());
1404
1405 unsafe {
1407 libc::close(50);
1408 libc::close(51);
1409 }
1410 let _ = std::fs::remove_file(&temp_file1);
1411 let _ = std::fs::remove_file(&temp_file2);
1412 }
1413
1414 #[test]
1415 fn test_fd_table_clear() {
1416 let mut fd_table = FileDescriptorTable::new();
1417
1418 let temp_file = "/tmp/rush_test_fd_clear.txt";
1420 std::fs::write(temp_file, "test content").unwrap();
1421
1422 fd_table
1428 .open_fd(50, temp_file, true, false, false, false)
1429 .unwrap();
1430 assert!(fd_table.is_open(50));
1431
1432 fd_table.clear();
1434 assert!(!fd_table.is_open(3));
1435
1436 let _ = std::fs::remove_file(temp_file);
1438 }
1439
1440 #[test]
1441 fn test_fd_table_get_stdio() {
1442 let mut fd_table = FileDescriptorTable::new();
1443
1444 let temp_file = "/tmp/rush_test_fd_stdio.txt";
1446 std::fs::write(temp_file, "test content").unwrap();
1447
1448 fd_table
1450 .open_fd(3, temp_file, true, false, false, false)
1451 .unwrap();
1452
1453 let stdio = fd_table.get_stdio(3);
1455 assert!(stdio.is_some());
1456
1457 let stdio = fd_table.get_stdio(5);
1459 assert!(stdio.is_none());
1460
1461 let _ = std::fs::remove_file(temp_file);
1463 }
1464
1465 #[test]
1466 fn test_fd_table_multiple_operations() {
1467 let mut fd_table = FileDescriptorTable::new();
1468
1469 let temp_file1 = "/tmp/rush_test_fd_multi1.txt";
1471 let temp_file2 = "/tmp/rush_test_fd_multi2.txt";
1472 std::fs::write(temp_file1, "test content 1").unwrap();
1473 std::fs::write(temp_file2, "test content 2").unwrap();
1474
1475 fd_table
1477 .open_fd(3, temp_file1, true, false, false, false)
1478 .unwrap();
1479 assert!(fd_table.is_open(3));
1480
1481 fd_table.duplicate_fd(3, 4).unwrap();
1483 assert!(fd_table.is_open(4));
1484
1485 fd_table
1487 .open_fd(5, temp_file2, true, false, false, false)
1488 .unwrap();
1489 assert!(fd_table.is_open(5));
1490
1491 fd_table.close_fd(4).unwrap();
1493 assert!(fd_table.is_closed(4));
1494 assert!(!fd_table.is_open(4));
1495
1496 assert!(fd_table.is_open(3));
1498 assert!(fd_table.is_open(5));
1499
1500 let _ = std::fs::remove_file(temp_file1);
1502 let _ = std::fs::remove_file(temp_file2);
1503 }
1504
1505 #[test]
1506 fn test_shell_state_has_fd_table() {
1507 let state = ShellState::new();
1508 let fd_table = state.fd_table.borrow();
1509 assert!(!fd_table.is_open(3));
1510 }
1511
1512 #[test]
1513 fn test_shell_state_fd_table_operations() {
1514 let state = ShellState::new();
1515
1516 let temp_file = "/tmp/rush_test_state_fd.txt";
1518 std::fs::write(temp_file, "test content").unwrap();
1519
1520 {
1522 let mut fd_table = state.fd_table.borrow_mut();
1523 fd_table
1524 .open_fd(3, temp_file, true, false, false, false)
1525 .unwrap();
1526 }
1527
1528 {
1530 let fd_table = state.fd_table.borrow();
1531 assert!(fd_table.is_open(3));
1532 }
1533
1534 let _ = std::fs::remove_file(temp_file);
1536 }
1537}