1use crate::Env;
50use crate::builtin::Builtin;
51use crate::builtin::Type;
52use crate::builtin::Type::{Elective, Extension, Mandatory, Special, Substitutive};
53use crate::builtin::is_posix_special_builtin_name;
54use crate::function::Function;
55use crate::option::{On, Portable, PosixlyCorrect};
56use crate::path::PathBuf;
57use crate::semantics::ExitStatus;
58use crate::system::IsExecutableFile;
59use crate::variable::Expansion;
60use crate::variable::PATH;
61use std::ffi::CStr;
62use std::ffi::CString;
63use std::rc::Rc;
64
65#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
72#[non_exhaustive]
73pub enum Availability {
74 Available,
76
77 NotPortable,
80}
81
82pub enum Target<S> {
95 Builtin {
97 builtin: Builtin<S>,
99
100 availability: Availability,
108
109 path: CString,
119 },
120
121 Function(Rc<Function<S>>),
123
124 External {
126 path: CString,
137 },
138}
139
140impl<S> Clone for Target<S> {
142 fn clone(&self) -> Self {
143 match self {
144 Self::Builtin {
145 builtin,
146 availability,
147 path,
148 } => Self::Builtin {
149 builtin: *builtin,
150 availability: *availability,
151 path: path.clone(),
152 },
153 Self::Function(f) => Self::Function(f.clone()),
154 Self::External { path } => Self::External { path: path.clone() },
155 }
156 }
157}
158
159impl<S> PartialEq for Target<S> {
160 fn eq(&self, other: &Self) -> bool {
161 match (self, other) {
162 (
163 Self::Builtin {
164 builtin: l_builtin,
165 availability: l_availability,
166 path: l_path,
167 },
168 Self::Builtin {
169 builtin: r_builtin,
170 availability: r_availability,
171 path: r_path,
172 },
173 ) => l_builtin == r_builtin && l_availability == r_availability && l_path == r_path,
174 (Self::Function(l), Self::Function(r)) => l == r,
175 (Self::External { path: l_path }, Self::External { path: r_path }) => l_path == r_path,
176 _ => false,
177 }
178 }
179}
180
181impl<S> Eq for Target<S> {}
182
183impl<S> std::fmt::Debug for Target<S> {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 match self {
186 Self::Builtin {
187 builtin,
188 availability,
189 path,
190 } => f
191 .debug_struct("Builtin")
192 .field("builtin", builtin)
193 .field("availability", availability)
194 .field("path", path)
195 .finish(),
196 Self::Function(func) => f.debug_tuple("Function").field(func).finish(),
197 Self::External { path } => f.debug_struct("External").field("path", path).finish(),
198 }
199 }
200}
201
202impl<S> From<Rc<Function<S>>> for Target<S> {
203 #[inline]
204 fn from(function: Rc<Function<S>>) -> Target<S> {
205 Target::Function(function)
206 }
207}
208
209impl<S> From<Function<S>> for Target<S> {
210 #[inline]
211 fn from(function: Function<S>) -> Target<S> {
212 Target::Function(function.into())
213 }
214}
215
216pub trait ClassifyEnv<S> {
222 #[must_use]
234 fn builtin(&self, name: &str) -> Option<(Builtin<S>, Availability)>;
235
236 #[must_use]
238 fn function(&self, name: &str) -> Option<&Rc<Function<S>>>;
239}
240
241pub trait PathEnv {
243 #[must_use]
249 fn path(&self) -> Expansion<'_>;
250
251 #[must_use]
253 fn is_executable_file(&self, path: &CStr) -> bool;
254 }
256
257impl<S: IsExecutableFile> PathEnv for Env<S> {
258 fn path(&self) -> Expansion<'_> {
263 self.variables
264 .get(PATH)
265 .and_then(|var| {
266 assert_eq!(var.quirk, None, "PATH does not support quirks");
267 var.value.as_ref()
268 })
269 .into()
270 }
271
272 fn is_executable_file(&self, path: &CStr) -> bool {
273 self.system.is_executable_file(path)
274 }
275}
276
277impl<S> ClassifyEnv<S> for Env<S> {
278 fn builtin(&self, name: &str) -> Option<(Builtin<S>, Availability)> {
279 let builtin = self.builtins.get(name).copied()?;
280
281 let found = builtin.r#type != Extension || self.options.get(PosixlyCorrect) != On;
284 if !found {
285 return None;
286 }
287
288 let availability = match builtin.r#type {
294 Elective | Extension if self.options.get(Portable) == On => Availability::NotPortable,
295 Special if self.options.get(Portable) == On && !is_posix_special_builtin_name(name) => {
296 Availability::NotPortable
297 }
298 Special | Mandatory | Elective | Extension | Substitutive => Availability::Available,
299 };
300
301 Some((builtin, availability))
302 }
303
304 #[inline]
305 fn function(&self, name: &str) -> Option<&Rc<Function<S>>> {
306 self.functions.get(name)
307 }
308}
309
310#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
312#[non_exhaustive]
313pub enum Unusable {
314 NotInPath,
317
318 NotPortable,
321}
322
323impl Unusable {
324 #[must_use]
329 pub const fn exit_status(self) -> ExitStatus {
330 match self {
331 Self::NotInPath => ExitStatus::NOT_FOUND,
332 Self::NotPortable => ExitStatus::NOEXEC,
333 }
334 }
335}
336
337impl std::fmt::Display for Unusable {
344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345 match self {
346 Self::NotInPath => "a substitutive built-in, so it cannot be used unless $PATH has \
347 an external utility of the same name"
348 .fmt(f),
349 Self::NotPortable => {
350 "not a POSIX built-in, so it cannot be used while the `portable` option is on"
351 .fmt(f)
352 }
353 }
354 }
355}
356
357#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
359#[non_exhaustive]
360pub enum Error {
361 NotFound,
363
364 Unusable(Unusable),
369}
370
371impl Error {
372 #[must_use]
378 pub const fn exit_status(self) -> ExitStatus {
379 match self {
380 Self::NotFound => ExitStatus::NOT_FOUND,
381 Self::Unusable(cause) => cause.exit_status(),
382 }
383 }
384}
385
386impl std::fmt::Display for Error {
391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392 match self {
393 Self::NotFound => "no command with this name".fmt(f),
394 Self::Unusable(cause) => cause.fmt(f),
395 }
396 }
397}
398
399impl From<Unusable> for Error {
400 #[inline]
401 fn from(unusable: Unusable) -> Self {
402 Self::Unusable(unusable)
403 }
404}
405
406pub fn resolve_builtin<E: PathEnv>(
425 env: &mut E,
426 name: &str,
427 r#type: Type,
428 availability: Availability,
429) -> Result<CString, Unusable> {
430 match availability {
431 Availability::NotPortable => Err(Unusable::NotPortable),
432
433 Availability::Available if r#type == Substitutive => {
436 search_path(env, name).ok_or(Unusable::NotInPath)
437 }
438
439 Availability::Available => Ok(CString::default()),
440 }
441}
442
443pub fn search<S, E: ClassifyEnv<S> + PathEnv>(env: &mut E, name: &str) -> Result<Target<S>, Error> {
455 let mut target = classify(env, name);
456
457 match &mut target {
458 Target::Builtin {
459 builtin,
460 availability,
461 path,
462 } => *path = resolve_builtin(env, name, builtin.r#type, *availability)?,
463
464 Target::External { path } => {
465 *path = if name.contains('/') {
466 CString::new(name).map_err(|_| Error::NotFound)?
468 } else {
469 search_path(env, name).ok_or(Error::NotFound)?
471 };
472 }
473
474 Target::Function(_) => {
475 }
477 }
478
479 Ok(target)
480}
481
482#[must_use]
497pub fn classify<S, E: ClassifyEnv<S>>(env: &E, name: &str) -> Target<S> {
498 if name.contains('/') {
499 return Target::External {
500 path: CString::default(),
501 };
502 }
503
504 let builtin = env.builtin(name);
505 if let Some((builtin, availability)) = builtin
506 && builtin.r#type == Special
507 {
508 let path = CString::default();
509 return Target::Builtin {
510 builtin,
511 availability,
512 path,
513 };
514 }
515
516 if let Some(function) = env.function(name) {
517 return Rc::clone(function).into();
518 }
519
520 if let Some((builtin, availability)) = builtin {
521 let path = CString::default();
522 return Target::Builtin {
523 builtin,
524 availability,
525 path,
526 };
527 }
528
529 Target::External {
530 path: CString::default(),
531 }
532}
533
534#[must_use]
544pub fn search_path<E: PathEnv>(env: &mut E, name: &str) -> Option<CString> {
545 env.path()
546 .split()
547 .filter_map(|dir| {
548 let candidate = PathBuf::from_iter([dir, name])
549 .into_unix_string()
550 .into_vec();
551 CString::new(candidate).ok()
552 })
553 .find(|path| env.is_executable_file(path))
554}
555
556#[allow(clippy::field_reassign_with_default, reason = "for readability")]
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use crate::function::{FunctionBody, FunctionBodyObject, FunctionSet};
561 use crate::option::Off;
562 use crate::source::Location;
563 use crate::variable::Value;
564 use assert_matches::assert_matches;
565 use std::collections::HashMap;
566 use std::collections::HashSet;
567
568 #[test]
569 fn env_builtin_returns_special_builtin() {
570 let mut env = Env::new_virtual();
571 let builtin = Builtin::new(Special, |_, _| unreachable!());
572 env.builtins.insert("foo", builtin);
573 assert_eq!(env.builtin("foo"), Some((builtin, Availability::Available)));
574 }
575
576 #[test]
577 fn env_builtin_returns_mandatory_builtin() {
578 let mut env = Env::new_virtual();
579 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
580 env.builtins.insert("foo", builtin);
581 assert_eq!(env.builtin("foo"), Some((builtin, Availability::Available)));
582 }
583
584 #[test]
585 fn env_builtin_returns_elective_builtin() {
586 let mut env = Env::new_virtual();
587 let builtin = Builtin::new(Elective, |_, _| unreachable!());
588 env.builtins.insert("foo", builtin);
589 assert_eq!(env.builtin("foo"), Some((builtin, Availability::Available)));
590 }
591
592 #[test]
593 fn env_builtin_returns_extension_builtin_if_not_posixly_correct() {
594 let mut env = Env::new_virtual();
595 let builtin = Builtin::new(Extension, |_, _| unreachable!());
596 env.builtins.insert("foo", builtin);
597 assert_eq!(env.options.get(PosixlyCorrect), Off);
598 assert_eq!(env.builtin("foo"), Some((builtin, Availability::Available)));
599 }
600
601 #[test]
602 fn env_builtin_does_not_return_extension_builtin_if_posixly_correct() {
603 let mut env = Env::new_virtual();
604 env.options.set(PosixlyCorrect, On);
605 let builtin = Builtin::new(Extension, |_, _| unreachable!());
606 env.builtins.insert("foo", builtin);
607 assert_eq!(env.builtin("foo"), None);
608 }
609
610 #[test]
611 fn env_builtin_rejects_elective_builtin_if_portable() {
612 let mut env = Env::new_virtual();
613 env.options.set(Portable, On);
614 let builtin = Builtin::new(Elective, |_, _| unreachable!());
615 env.builtins.insert("foo", builtin);
616 assert_eq!(
617 env.builtin("foo"),
618 Some((builtin, Availability::NotPortable))
619 );
620 }
621
622 #[test]
623 fn env_builtin_rejects_extension_builtin_if_portable() {
624 let mut env = Env::new_virtual();
625 env.options.set(Portable, On);
626 let builtin = Builtin::new(Extension, |_, _| unreachable!());
627 env.builtins.insert("foo", builtin);
628 assert_eq!(
629 env.builtin("foo"),
630 Some((builtin, Availability::NotPortable))
631 );
632 }
633
634 #[test]
635 fn env_builtin_accepts_posix_builtins_if_portable() {
636 for r#type in [Mandatory, Substitutive] {
637 let mut env = Env::new_virtual();
638 env.options.set(Portable, On);
639 let builtin = Builtin::new(r#type, |_, _| unreachable!());
640 env.builtins.insert("foo", builtin);
641 assert_eq!(
642 env.builtin("foo"),
643 Some((builtin, Availability::Available)),
644 "type={type:?}"
645 );
646 }
647 }
648
649 #[test]
650 fn env_builtin_accepts_special_builtin_with_posix_name_if_portable() {
651 let mut env = Env::new_virtual();
652 env.options.set(Portable, On);
653 let builtin = Builtin::new(Special, |_, _| unreachable!());
654 env.builtins.insert(".", builtin);
655 assert_eq!(env.builtin("."), Some((builtin, Availability::Available)));
656 }
657
658 #[test]
659 fn env_builtin_rejects_special_builtin_with_non_posix_name_if_portable() {
660 let mut env = Env::new_virtual();
661 env.options.set(Portable, On);
662 let builtin = Builtin::new(Special, |_, _| unreachable!());
663 env.builtins.insert("source", builtin);
664 assert_eq!(
665 env.builtin("source"),
666 Some((builtin, Availability::NotPortable))
667 );
668 }
669
670 #[test]
671 fn env_builtin_returns_substitutive_builtin() {
672 let mut env = Env::new_virtual();
675 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
676 env.builtins.insert("foo", builtin);
677 assert_eq!(env.builtin("foo"), Some((builtin, Availability::Available)));
678 }
679
680 #[test]
681 fn unusable_exit_status() {
682 assert_eq!(Unusable::NotInPath.exit_status(), ExitStatus(127));
683 assert_eq!(Unusable::NotPortable.exit_status(), ExitStatus(126));
684 }
685
686 #[test]
687 fn unusable_display() {
688 assert_eq!(
689 Unusable::NotInPath.to_string(),
690 "a substitutive built-in, so it cannot be used unless $PATH has an external utility \
691 of the same name",
692 );
693 assert_eq!(
694 Unusable::NotPortable.to_string(),
695 "not a POSIX built-in, so it cannot be used while the `portable` option is on",
696 );
697 }
698
699 #[test]
700 fn error_exit_status() {
701 assert_eq!(Error::NotFound.exit_status(), ExitStatus(127));
702 assert_eq!(
703 Error::Unusable(Unusable::NotInPath).exit_status(),
704 ExitStatus(127),
705 );
706 assert_eq!(
707 Error::Unusable(Unusable::NotPortable).exit_status(),
708 ExitStatus(126),
709 );
710 }
711
712 #[test]
713 fn error_display() {
714 assert_eq!(Error::NotFound.to_string(), "no command with this name");
715 assert_eq!(
716 Error::Unusable(Unusable::NotPortable).to_string(),
717 "not a POSIX built-in, so it cannot be used while the `portable` option is on",
718 );
719 }
720
721 #[derive(Default)]
722 struct DummyEnv {
723 builtins: HashMap<&'static str, (Builtin<()>, Availability)>,
724 functions: FunctionSet<()>,
725 path: Expansion<'static>,
726 executables: HashSet<String>,
727 }
728
729 impl PathEnv for DummyEnv {
730 fn path(&self) -> Expansion<'_> {
731 self.path.as_ref()
732 }
733 fn is_executable_file(&self, path: &CStr) -> bool {
734 if let Ok(path) = path.to_str() {
735 self.executables.contains(path)
736 } else {
737 false
738 }
739 }
740 }
741
742 impl ClassifyEnv<()> for DummyEnv {
743 fn builtin(&self, name: &str) -> Option<(Builtin<()>, Availability)> {
744 self.builtins.get(name).copied()
745 }
746 fn function(&self, name: &str) -> Option<&Rc<Function<()>>> {
747 self.functions.get(name)
748 }
749 }
750
751 #[derive(Clone, Debug)]
752 struct FunctionBodyStub;
753
754 impl std::fmt::Display for FunctionBodyStub {
755 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
756 unreachable!()
757 }
758 }
759 impl<S> FunctionBody<S> for FunctionBodyStub {
760 async fn execute(&self, _: &mut Env<S>) -> crate::semantics::Result {
761 unreachable!()
762 }
763 }
764
765 fn function_body_stub<S>() -> Rc<dyn FunctionBodyObject<S>> {
766 Rc::new(FunctionBodyStub)
767 }
768
769 #[test]
770 fn resolve_builtin_returns_empty_path_for_non_substitutive_builtin() {
771 let mut env = DummyEnv::default();
772
773 let result = resolve_builtin(&mut env, "foo", Mandatory, Availability::Available);
774
775 assert_eq!(result, Ok(CString::default()));
776 }
777
778 #[test]
779 fn resolve_builtin_returns_external_path_for_substitutive_builtin() {
780 let mut env = DummyEnv::default();
781 env.path = Expansion::from("/bin");
782 env.executables.insert("/bin/foo".to_string());
783
784 let result = resolve_builtin(&mut env, "foo", Substitutive, Availability::Available);
785
786 assert_eq!(result, Ok(c"/bin/foo".to_owned()));
787 }
788
789 #[test]
790 fn resolve_builtin_rejects_substitutive_builtin_missing_in_path() {
791 let mut env = DummyEnv::default();
792
793 let result = resolve_builtin(&mut env, "foo", Substitutive, Availability::Available);
794
795 assert_eq!(result, Err(Unusable::NotInPath));
796 }
797
798 #[test]
799 fn resolve_builtin_rejects_unavailable_builtin() {
800 let mut env = DummyEnv::default();
803 env.path = Expansion::from("/bin");
804 env.executables.insert("/bin/foo".to_string());
805
806 let result = resolve_builtin(&mut env, "foo", Substitutive, Availability::NotPortable);
807
808 assert_eq!(result, Err(Unusable::NotPortable));
809 }
810
811 #[test]
812 fn nothing_is_found_in_empty_env() {
813 let mut env = DummyEnv::default();
814 let target = search(&mut env, "foo");
815 assert_eq!(target, Err(Error::NotFound));
816 }
817
818 #[test]
819 fn nothing_is_found_with_name_unmatched() {
820 let mut env = DummyEnv::default();
821 env.builtins.insert(
822 "foo",
823 (
824 Builtin::new(Special, |_, _| unreachable!()),
825 Availability::Available,
826 ),
827 );
828 let function = Function::new("foo", function_body_stub(), Location::dummy(""));
829 env.functions.define(function).unwrap();
830
831 let target = search(&mut env, "bar");
832 assert_eq!(target, Err(Error::NotFound));
833 }
834
835 #[test]
836 fn classify_defaults_to_external() {
837 let env = DummyEnv::default();
840 let target = classify(&env, "foo");
841 assert_eq!(
842 target,
843 Target::External {
844 path: CString::default()
845 }
846 );
847 }
848
849 #[test]
850 fn special_builtin_is_found() {
851 let mut env = DummyEnv::default();
852 let builtin = Builtin::new(Special, |_, _| unreachable!());
853 env.builtins
854 .insert("foo", (builtin, Availability::Available));
855
856 assert_matches!(
857 search(&mut env, "foo"),
858 Ok(Target::Builtin { builtin: result, availability, path }) => {
859 assert_eq!(result.r#type, builtin.r#type);
860 assert_eq!(availability, Availability::Available);
861 assert_eq!(*path, *c"");
862 }
863 );
864 assert_matches!(
865 classify(&env, "foo"),
866 Target::Builtin { builtin: result, availability, path } => {
867 assert_eq!(result.r#type, builtin.r#type);
868 assert_eq!(availability, Availability::Available);
869 assert_eq!(*path, *c"");
870 }
871 );
872 }
873
874 #[test]
875 fn function_is_found_if_not_hidden_by_special_builtin() {
876 let mut env = DummyEnv::default();
877 let function = Rc::new(Function::new(
878 "foo",
879 function_body_stub(),
880 Location::dummy("location"),
881 ));
882 env.functions.define(function.clone()).unwrap();
883
884 assert_matches!(search(&mut env, "foo"), Ok(Target::Function(result)) => {
885 assert_eq!(result, function);
886 });
887 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
888 assert_eq!(result, function);
889 });
890 }
891
892 #[test]
893 fn special_builtin_takes_priority_over_function() {
894 let mut env = DummyEnv::default();
895 let builtin = Builtin::new(Special, |_, _| unreachable!());
896 env.builtins
897 .insert("foo", (builtin, Availability::Available));
898 let function = Function::new("foo", function_body_stub(), Location::dummy("location"));
899 env.functions.define(function).unwrap();
900
901 assert_matches!(
902 search(&mut env, "foo"),
903 Ok(Target::Builtin { builtin: result, availability, path }) => {
904 assert_eq!(result.r#type, builtin.r#type);
905 assert_eq!(availability, Availability::Available);
906 assert_eq!(*path, *c"");
907 }
908 );
909 assert_matches!(
910 classify(&env, "foo"),
911 Target::Builtin { builtin: result, availability, path } => {
912 assert_eq!(result.r#type, builtin.r#type);
913 assert_eq!(availability, Availability::Available);
914 assert_eq!(*path, *c"");
915 }
916 );
917 }
918
919 #[test]
920 fn mandatory_builtin_is_found_if_not_hidden_by_function() {
921 let mut env = DummyEnv::default();
922 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
923 env.builtins
924 .insert("foo", (builtin, Availability::Available));
925
926 assert_matches!(
927 search(&mut env, "foo"),
928 Ok(Target::Builtin { builtin: result, availability, path }) => {
929 assert_eq!(result.r#type, builtin.r#type);
930 assert_eq!(availability, Availability::Available);
931 assert_eq!(*path, *c"");
932 }
933 );
934 assert_matches!(
935 classify(&env, "foo"),
936 Target::Builtin { builtin: result, availability, path } => {
937 assert_eq!(result.r#type, builtin.r#type);
938 assert_eq!(availability, Availability::Available);
939 assert_eq!(*path, *c"");
940 }
941 );
942 }
943
944 #[test]
945 fn elective_builtin_is_found_if_not_hidden_by_function() {
946 let mut env = DummyEnv::default();
947 let builtin = Builtin::new(Elective, |_, _| unreachable!());
948 env.builtins
949 .insert("foo", (builtin, Availability::Available));
950
951 assert_matches!(
952 search(&mut env, "foo"),
953 Ok(Target::Builtin { builtin: result, availability, path }) => {
954 assert_eq!(result.r#type, builtin.r#type);
955 assert_eq!(availability, Availability::Available);
956 assert_eq!(*path, *c"");
957 }
958 );
959 assert_matches!(
960 classify(&env, "foo"),
961 Target::Builtin { builtin: result, availability, path } => {
962 assert_eq!(result.r#type, builtin.r#type);
963 assert_eq!(availability, Availability::Available);
964 assert_eq!(*path, *c"");
965 }
966 );
967 }
968
969 #[test]
970 fn extension_builtin_is_found_if_not_hidden_by_function_or_option() {
971 let mut env = DummyEnv::default();
972 let builtin = Builtin::new(Extension, |_, _| unreachable!());
973 env.builtins
974 .insert("foo", (builtin, Availability::Available));
975
976 assert_matches!(
977 search(&mut env, "foo"),
978 Ok(Target::Builtin { builtin: result, availability, path }) => {
979 assert_eq!(result.r#type, builtin.r#type);
980 assert_eq!(availability, Availability::Available);
981 assert_eq!(*path, *c"");
982 }
983 );
984 assert_matches!(
985 classify(&env, "foo"),
986 Target::Builtin { builtin: result, availability, path } => {
987 assert_eq!(result.r#type, builtin.r#type);
988 assert_eq!(availability, Availability::Available);
989 assert_eq!(*path, *c"");
990 }
991 );
992 }
993
994 #[test]
995 fn function_takes_priority_over_mandatory_builtin() {
996 let mut env = DummyEnv::default();
997 env.builtins.insert(
998 "foo",
999 (
1000 Builtin::new(Mandatory, |_, _| unreachable!()),
1001 Availability::Available,
1002 ),
1003 );
1004
1005 let function = Rc::new(Function::new(
1006 "foo",
1007 function_body_stub(),
1008 Location::dummy("location"),
1009 ));
1010 env.functions.define(function.clone()).unwrap();
1011
1012 assert_matches!(search(&mut env, "foo"), Ok(Target::Function(result)) => {
1013 assert_eq!(result, function);
1014 });
1015 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
1016 assert_eq!(result, function);
1017 });
1018 }
1019
1020 #[test]
1021 fn function_takes_priority_over_elective_builtin() {
1022 let mut env = DummyEnv::default();
1023 env.builtins.insert(
1024 "foo",
1025 (
1026 Builtin::new(Elective, |_, _| unreachable!()),
1027 Availability::Available,
1028 ),
1029 );
1030
1031 let function = Rc::new(Function::new(
1032 "foo",
1033 function_body_stub(),
1034 Location::dummy("location"),
1035 ));
1036 env.functions.define(function.clone()).unwrap();
1037
1038 assert_matches!(search(&mut env, "foo"), Ok(Target::Function(result)) => {
1039 assert_eq!(result, function);
1040 });
1041 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
1042 assert_eq!(result, function);
1043 });
1044 }
1045
1046 #[test]
1047 fn function_takes_priority_over_extension_builtin() {
1048 let mut env = DummyEnv::default();
1049 env.builtins.insert(
1050 "foo",
1051 (
1052 Builtin::new(Extension, |_, _| unreachable!()),
1053 Availability::Available,
1054 ),
1055 );
1056
1057 let function = Rc::new(Function::new(
1058 "foo",
1059 function_body_stub(),
1060 Location::dummy("location"),
1061 ));
1062 env.functions.define(function.clone()).unwrap();
1063
1064 assert_matches!(search(&mut env, "foo"), Ok(Target::Function(result)) => {
1065 assert_eq!(result, function);
1066 });
1067 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
1068 assert_eq!(result, function);
1069 });
1070 }
1071
1072 #[test]
1073 fn substitutive_builtin_is_found_if_external_executable_exists() {
1074 let mut env = DummyEnv::default();
1075 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
1076 env.builtins
1077 .insert("foo", (builtin, Availability::Available));
1078 env.path = Expansion::from("/bin");
1079 env.executables.insert("/bin/foo".to_string());
1080
1081 assert_matches!(
1082 search(&mut env, "foo"),
1083 Ok(Target::Builtin { builtin: result, availability, path }) => {
1084 assert_eq!(result.r#type, builtin.r#type);
1085 assert_eq!(availability, Availability::Available);
1086 assert_eq!(*path, *c"/bin/foo");
1087 }
1088 );
1089 assert_matches!(
1090 classify(&env, "foo"),
1091 Target::Builtin { builtin: result, availability, path } => {
1092 assert_eq!(result.r#type, builtin.r#type);
1093 assert_eq!(availability, Availability::Available);
1094 assert_eq!(*path, *c"");
1095 }
1096 );
1097 }
1098
1099 #[test]
1100 fn substitutive_builtin_is_unusable_without_external_executable() {
1101 let mut env = DummyEnv::default();
1102 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
1103 env.builtins
1104 .insert("foo", (builtin, Availability::Available));
1105
1106 let target = search(&mut env, "foo");
1107 assert_eq!(target, Err(Error::Unusable(Unusable::NotInPath)));
1108 }
1109
1110 #[test]
1111 fn builtin_rejected_by_options_is_unusable() {
1112 let mut env = DummyEnv::default();
1113 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
1114 env.builtins
1115 .insert("foo", (builtin, Availability::NotPortable));
1116
1117 let target = search(&mut env, "foo");
1118 assert_eq!(target, Err(Error::Unusable(Unusable::NotPortable)));
1119 }
1120
1121 #[test]
1122 fn builtin_rejected_by_options_is_still_classified() {
1123 let mut env = DummyEnv::default();
1126 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
1127 env.builtins
1128 .insert("foo", (builtin, Availability::NotPortable));
1129
1130 assert_matches!(
1131 classify(&env, "foo"),
1132 Target::Builtin { builtin: result, availability, path } => {
1133 assert_eq!(result.r#type, builtin.r#type);
1134 assert_eq!(availability, Availability::NotPortable);
1135 assert_eq!(*path, *c"");
1136 }
1137 );
1138 }
1139
1140 #[test]
1141 fn substitutive_builtin_is_classified_even_without_external_executable() {
1142 let mut env = DummyEnv::default();
1143 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
1144 env.builtins
1145 .insert("foo", (builtin, Availability::Available));
1146
1147 assert_matches!(
1148 classify(&env, "foo"),
1149 Target::Builtin { builtin: result, availability, path } => {
1150 assert_eq!(result.r#type, builtin.r#type);
1151 assert_eq!(availability, Availability::Available);
1152 assert_eq!(*path, *c"");
1153 }
1154 );
1155 }
1156
1157 #[test]
1158 fn function_takes_priority_over_substitutive_builtin() {
1159 let mut env = DummyEnv::default();
1160 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
1161 env.builtins
1162 .insert("foo", (builtin, Availability::Available));
1163 env.path = Expansion::from("/bin");
1164 env.executables.insert("/bin/foo".to_string());
1165
1166 let function = Rc::new(Function::new(
1167 "foo",
1168 function_body_stub(),
1169 Location::dummy("location"),
1170 ));
1171 env.functions.define(function.clone()).unwrap();
1172
1173 assert_matches!(search(&mut env, "foo"), Ok(Target::Function(result)) => {
1174 assert_eq!(result, function);
1175 });
1176 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
1177 assert_eq!(result, function);
1178 });
1179 }
1180
1181 #[test]
1182 fn external_utility_is_found_if_external_executable_exists() {
1183 let mut env = DummyEnv::default();
1184 env.path = Expansion::from("/bin");
1185 env.executables.insert("/bin/foo".to_string());
1186
1187 assert_matches!(search(&mut env, "foo"), Ok(Target::External { path }) => {
1188 assert_eq!(*path, *c"/bin/foo");
1189 });
1190 assert_matches!(classify(&env, "foo"), Target::External { path } => {
1191 assert_eq!(*path, *c"");
1192 });
1193 }
1194
1195 #[test]
1196 fn returns_external_utility_if_name_contains_slash() {
1197 let mut env = DummyEnv::default();
1199 let builtin = Builtin::new(Special, |_, _| unreachable!());
1202 env.builtins
1203 .insert("bar/baz", (builtin, Availability::Available));
1204
1205 assert_matches!(search(&mut env, "bar/baz"), Ok(Target::External { path }) => {
1206 assert_eq!(*path, *c"bar/baz");
1207 });
1208 assert_matches!(classify(&env, "bar/baz"), Target::External { path } => {
1209 assert_eq!(*path, *c"");
1210 });
1211 }
1212
1213 #[test]
1214 fn external_target_is_first_executable_found_in_path_scalar() {
1215 let mut env = DummyEnv::default();
1216 env.path = Expansion::from("/usr/local/bin:/usr/bin:/bin");
1217 env.executables.insert("/usr/bin/foo".to_string());
1218 env.executables.insert("/bin/foo".to_string());
1219
1220 assert_matches!(search(&mut env, "foo"), Ok(Target::External { path }) => {
1221 assert_eq!(*path, *c"/usr/bin/foo");
1222 });
1223
1224 env.executables.insert("/usr/local/bin/foo".to_string());
1225
1226 assert_matches!(search(&mut env, "foo"), Ok(Target::External { path }) => {
1227 assert_eq!(*path, *c"/usr/local/bin/foo");
1228 });
1229 }
1230
1231 #[test]
1232 fn external_target_is_first_executable_found_in_path_array() {
1233 let mut env = DummyEnv::default();
1234 env.path = Expansion::from(Value::array(["/usr/local/bin", "/usr/bin", "/bin"]));
1235 env.executables.insert("/usr/bin/foo".to_string());
1236 env.executables.insert("/bin/foo".to_string());
1237
1238 assert_matches!(search(&mut env, "foo"), Ok(Target::External { path }) => {
1239 assert_eq!(*path, *c"/usr/bin/foo");
1240 });
1241
1242 env.executables.insert("/usr/local/bin/foo".to_string());
1243
1244 assert_matches!(search(&mut env, "foo"), Ok(Target::External { path }) => {
1245 assert_eq!(*path, *c"/usr/local/bin/foo");
1246 });
1247 }
1248
1249 #[test]
1250 fn empty_string_in_path_names_current_directory() {
1251 let mut env = DummyEnv::default();
1252 env.path = Expansion::from("/x::/y");
1253 env.executables.insert("foo".to_string());
1254
1255 assert_matches!(search(&mut env, "foo"), Ok(Target::External { path }) => {
1256 assert_eq!(*path, *c"foo");
1257 });
1258 }
1259}