yash_env/
variable.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Items for shell variables
18//!
19//! A [`Variable`] is a named parameter that can be assigned and exported. It is
20//! defined in a context of a variable set. A [`VariableSet`] is a stack of
21//! contexts that can be pushed and popped. Each context has a map of
22//! name-variable pairs that effectively manages the variables.
23//!
24//! # Variable sets and contexts
25//!
26//! The variable set is a component of the shell environment ([`Env`]). It
27//! contains a non-empty stack of contexts. The first context in the stack is
28//! called the _base context_, and it is always present. Other contexts can be
29//! pushed and popped on a last-in-first-out basis.
30//!
31//! Each context is a map of name-variable pairs. Variables in a context hide
32//! those with the same name in lower contexts. You cannot access such hidden
33//! variables until the hiding variables are removed or the context containing
34//! them is popped.
35//!
36//! There are two types of [`Context`]s: regular and volatile. A regular context
37//! is the default context type and may have positional parameters. A volatile
38//! context is used for holding temporary variables when executing a built-in or
39//! function. The context types and [`Scope`] affect the behavior of variable
40//! assignment. The base context is always a regular context.
41//!
42//! Note that the notion of name-variable pairs is directly implemented in the
43//! [`VariableSet`] struct, and is not visible in the [`Context`] enum.
44//!
45//! ## Context guards
46//!
47//! This module provides guards to ensure contexts are pushed and popped
48//! correctly. The push function returns a guard that will pop the context when
49//! dropped. Implementing `Deref` and `DerefMut`, the guard allows access to the
50//! borrowed variable set or environment. To push a new context and acquire a
51//! guard, use [`VariableSet::push_context`] or [`Env::push_context`].
52//!
53//! # Variables
54//!
55//! An instance of [`Variable`] represents the value and attributes of a shell
56//! variable. Although all the fields of a variable are public, you cannot
57//! obtain a mutable reference to a variable from a variable set directly. You
58//! need to use [`VariableRefMut`] to modify a variable.
59//!
60//! ## Variable names and initial values
61//!
62//! This module defines constants for the names and initial values of some
63//! variables. The constants are used in the shell initialization process to
64//! create and assign the variables. The documentation for each name constant
65//! describes the variable's purpose and initial value.
66//!
67//! # Examples
68//!
69//! ```
70//! use yash_env::variable::{Context, Scope, VariableSet};
71//! let mut set = VariableSet::new();
72//!
73//! // Define a variable in the base context
74//! let mut var = set.get_or_new("foo", Scope::Global);
75//! var.assign("hello", None).unwrap();
76//!
77//! // Push a new context
78//! let mut guard = set.push_context(Context::default());
79//!
80//! // The variable is still visible
81//! assert_eq!(guard.get("foo").unwrap().value, Some("hello".into()));
82//!
83//! // Defining a new variable in the new context hides the previous variable
84//! let mut var = guard.get_or_new("foo", Scope::Local);
85//! var.assign("world", None).unwrap();
86//!
87//! // The new variable is visible
88//! assert_eq!(guard.get("foo").unwrap().value, Some("world".into()));
89//!
90//! // Pop the context
91//! drop(guard);
92//!
93//! // The previous variable is visible again
94//! assert_eq!(set.get("foo").unwrap().value, Some("hello".into()));
95//! ```
96
97#[cfg(doc)]
98use crate::Env;
99use crate::semantics::Field;
100use crate::source::Location;
101use itertools::Itertools;
102use std::borrow::Borrow;
103use std::collections::HashMap;
104use std::collections::hash_map::Entry::{Occupied, Vacant};
105use std::ffi::CString;
106use std::fmt::Write;
107use std::hash::Hash;
108use std::iter::FusedIterator;
109use thiserror::Error;
110
111mod value;
112
113pub use self::value::QuotedValue;
114pub use self::value::Value::{self, Array, Scalar};
115
116mod quirk;
117
118pub use self::quirk::Expansion;
119pub use self::quirk::Quirk;
120
121mod main;
122
123pub use self::main::AssignError;
124pub use self::main::Variable;
125pub use self::main::VariableRefMut;
126
127mod constants;
128
129// Export variable name and initial value constants
130pub use self::constants::*;
131
132#[derive(Clone, Debug, Eq, PartialEq)]
133struct VariableInContext {
134    variable: Variable,
135    context_index: usize,
136}
137
138/// Positional parameters
139#[derive(Clone, Debug, Default, Eq, PartialEq)]
140pub struct PositionalParams {
141    /// Values of positional parameters
142    pub values: Vec<String>,
143    /// Location of the last modification of positional parameters
144    pub last_modified_location: Option<Location>,
145}
146
147impl PositionalParams {
148    /// Creates a `PositionalParams` instance from fields.
149    ///
150    /// The given iterator should be a non-empty sequence of fields. The first
151    /// field is the name of the command whose origin is used as the
152    /// `last_modified_location`. The rest of the fields are the values of
153    /// positional parameters.
154    pub fn from_fields<I>(fields: I) -> Self
155    where
156        I: IntoIterator<Item = Field>,
157    {
158        let mut fields = fields.into_iter();
159        let last_modified_location = fields.next().map(|field| field.origin);
160        let values = fields.map(|field| field.value).collect();
161        Self {
162            values,
163            last_modified_location,
164        }
165    }
166}
167
168/// Variable context
169///
170/// This enum defines the type of a context. The context type affects the
171/// behavior of variable [assignment](VariableRefMut::assign). A regular context
172/// is the default context type and may have positional parameters. A volatile
173/// context is used for holding temporary variables when executing a built-in or
174/// function.
175#[derive(Clone, Debug, Eq, PartialEq)]
176pub enum Context {
177    /// Context for normal assignments.
178    ///
179    /// The base context is a regular context. Every function invocation also
180    /// creates a regular context for local assignments and positional
181    /// parameters.
182    Regular { positional_params: PositionalParams },
183
184    /// Context for temporary assignments.
185    ///
186    /// A volatile context is used for holding temporary variables when
187    /// executing a built-in or function.
188    Volatile,
189}
190
191impl Default for Context {
192    fn default() -> Self {
193        Context::Regular {
194            positional_params: Default::default(),
195        }
196    }
197}
198
199/// Collection of variables.
200///
201/// See the [module documentation](self) for details.
202#[derive(Clone, Debug, Eq, PartialEq)]
203pub struct VariableSet {
204    /// Hash map containing all variables.
205    ///
206    /// The value of a hash map entry is a stack of variables defined in
207    /// contexts, sorted in the ascending order of the context index.
208    ///
209    /// Having the variables of all the contexts in this single hash map makes
210    /// the variable search faster than having a separate hash map for each
211    /// context.
212    all_variables: HashMap<String, Vec<VariableInContext>>,
213
214    /// Stack of contexts.
215    ///
216    /// The stack can never be empty since the base context is always the first
217    /// item.
218    contexts: Vec<Context>,
219}
220
221impl Default for VariableSet {
222    fn default() -> Self {
223        VariableSet {
224            all_variables: Default::default(),
225            contexts: vec![Context::default()],
226        }
227    }
228}
229
230/// Choice of a context in which a variable is assigned or searched for.
231///
232/// For the meaning of the variants of this enum, see the docs for the functions
233/// that use it: [`VariableRefMut::assign`] and [`VariableSet::iter`].
234#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub enum Scope {
236    Global,
237    Local,
238    Volatile,
239}
240
241/// Error that occurs when unsetting a read-only variable
242#[derive(Clone, Debug, Eq, Error, PartialEq)]
243#[error("cannot unset read-only variable `{name}`")]
244pub struct UnsetError<'a> {
245    /// Variable name.
246    pub name: &'a str,
247    /// Location where the existing variable was made read-only.
248    pub read_only_location: &'a Location,
249}
250
251/// Iterator of variables
252///
253/// [`VariableSet::iter`] returns this iterator.
254#[derive(Clone, Debug)]
255pub struct Iter<'a> {
256    inner: std::collections::hash_map::Iter<'a, String, Vec<VariableInContext>>,
257    min_context_index: usize,
258}
259
260impl VariableSet {
261    /// Creates an empty variable set.
262    #[must_use]
263    pub fn new() -> VariableSet {
264        Default::default()
265    }
266
267    /// Gets a reference to the variable with the specified name.
268    ///
269    /// This method searches for a variable of the specified name and returns a
270    /// reference to it if found. If variables with the same name are defined in
271    /// multiple contexts, the one in the topmost context is considered
272    /// _visible_ and returned. To limit the search to the local context, use
273    /// [`get_scoped`](Self::get_scoped).
274    ///
275    /// You cannot retrieve positional parameters using this function.
276    /// See [`positional_params`](Self::positional_params).
277    #[must_use]
278    pub fn get<N>(&self, name: &N) -> Option<&Variable>
279    where
280        String: Borrow<N>,
281        N: Hash + Eq + ?Sized,
282    {
283        Some(&self.all_variables.get(name)?.last()?.variable)
284    }
285
286    /// Computes the index of the topmost regular context.
287    fn index_of_topmost_regular_context(contexts: &[Context]) -> usize {
288        contexts
289            .iter()
290            .rposition(|context| matches!(context, Context::Regular { .. }))
291            .expect("base context has gone")
292    }
293
294    /// Computes the index of the context that matches the specified scope.
295    fn index_of_context(scope: Scope, contexts: &[Context]) -> usize {
296        match scope {
297            Scope::Global => 0,
298            Scope::Local => Self::index_of_topmost_regular_context(contexts),
299            Scope::Volatile => Self::index_of_topmost_regular_context(contexts) + 1,
300        }
301    }
302
303    /// Returns a reference to the variable with the specified name.
304    ///
305    /// This method searches for a variable of the specified name and returns a
306    /// reference to it if found. The `scope` parameter determines the context
307    /// the variable is searched for:
308    ///
309    /// - If the scope is `Global`, the variable is searched for in all contexts
310    ///   from the topmost to the base context.
311    /// - If the scope is `Local`, the variable is searched for from the topmost
312    ///   to the topmost regular context.
313    /// - If the scope is `Volatile`, the variable is searched for in volatile
314    ///   contexts above the topmost regular context.
315    ///
316    /// `get_scoped` with `Scope::Global` is equivalent to [`get`](Self::get).
317    ///
318    /// You cannot retrieve positional parameters using this function.
319    /// See [`positional_params`](Self::positional_params).
320    #[must_use]
321    pub fn get_scoped<N>(&self, name: &N, scope: Scope) -> Option<&Variable>
322    where
323        String: Borrow<N>,
324        N: Hash + Eq + ?Sized,
325    {
326        let index = Self::index_of_context(scope, &self.contexts);
327        self.all_variables
328            .get(name)?
329            .last()
330            .filter(|vic| vic.context_index >= index)
331            .map(|vic| &vic.variable)
332    }
333
334    /// Gets a mutable reference to the variable with the specified name.
335    ///
336    /// You use this method to create or modify a variable.
337    /// This method searches for a variable of the specified name, and returns
338    /// a mutable reference to it if found. Otherwise, this method creates a new
339    /// variable and returns a mutable reference to it. The `scope` parameter
340    /// determines the context the variable is searched for or created in:
341    ///
342    /// - If the scope is `Global`, an existing variable is searched for like
343    ///   [`get`](Self::get). If a variable is found in a [regular] context, the
344    ///   variable is returned. If there is no variable, a new defaulted
345    ///   variable is created in the base context and returned.
346    ///   - If a variable is in a [volatile] context, this method removes the
347    ///     variable from the volatile context and continues searching for a
348    ///     variable in a lower context. If a variable is found in a regular
349    ///     context, it is replaced with the variable removed from the volatile
350    ///     context. Otherwise, the removed variable is moved to the base
351    ///     context. In either case, the moved variable is returned.
352    /// - If the scope is `Local`, the behavior is the same as `Global` except
353    ///   that any contexts below the topmost [regular] context are ignored.
354    ///   If a variable is found in the topmost regular context, the variable is
355    ///   returned. If there is no variable, a new defaulted variable is created
356    ///   in the topmost regular context and returned.
357    ///   - If a variable is in a [volatile] context above the topmost regular
358    ///     context, the variable is moved to the topmost regular context,
359    ///     overwriting the existing variable if any. The moved variable is
360    ///     returned.
361    /// - If the scope is `Volatile`, this method requires the topmost context
362    ///   to be [volatile]. Otherwise, this method will **panic!** If the
363    ///   topmost context is volatile, an existing variable is searched for like
364    ///   [`get`](Self::get). If a variable is found in the topmost context, the
365    ///   variable is returned. If a variable is found in a lower context, the
366    ///   variable is cloned to the topmost context and returned. If there is
367    ///   no variable, a new defaulted variable is created in the topmost
368    ///   context and returned.
369    ///
370    /// You cannot modify positional parameters using this method.
371    /// See [`positional_params_mut`](Self::positional_params_mut).
372    ///
373    /// This method does not apply the [`AllExport`](crate::option::AllExport)
374    /// option.  You need to [export](VariableRefMut::export) the variable
375    /// yourself, or use [`Env::get_or_create_variable`] to get the option
376    /// applied automatically.
377    ///
378    /// [regular]: Context::Regular
379    /// [volatile]: Context::Volatile
380    #[inline]
381    pub fn get_or_new<S: Into<String>>(&mut self, name: S, scope: Scope) -> VariableRefMut<'_> {
382        self.get_or_new_impl(name.into(), scope)
383    }
384
385    fn get_or_new_impl(&mut self, name: String, scope: Scope) -> VariableRefMut<'_> {
386        let stack = match self.all_variables.entry(name) {
387            Vacant(vacant) => vacant.insert(Vec::new()),
388            Occupied(occupied) => occupied.into_mut(),
389        };
390        let context_index = match scope {
391            Scope::Global => 0,
392            Scope::Local => Self::index_of_topmost_regular_context(&self.contexts),
393            Scope::Volatile => self.contexts.len() - 1,
394        };
395
396        match scope {
397            Scope::Global | Scope::Local => 'branch: {
398                let mut removed_volatile_variable = None;
399
400                // Search the stack for a variable to return, and add one if not found.
401                // If a variable is in a volatile context, temporarily move it to
402                // removed_volatile_variable and put it in the target context before returning it.
403                while let Some(var) = stack.last_mut() {
404                    if var.context_index < context_index {
405                        break;
406                    }
407                    match self.contexts[var.context_index] {
408                        Context::Regular { .. } => {
409                            if let Some(removed_volatile_variable) = removed_volatile_variable {
410                                var.variable = removed_volatile_variable;
411                            }
412                            break 'branch;
413                        }
414                        Context::Volatile => {
415                            removed_volatile_variable.get_or_insert(stack.pop().unwrap().variable);
416                        }
417                    }
418                }
419
420                stack.push(VariableInContext {
421                    variable: removed_volatile_variable.unwrap_or_default(),
422                    context_index,
423                });
424            }
425
426            Scope::Volatile => {
427                assert_eq!(
428                    self.contexts[context_index],
429                    Context::Volatile,
430                    "no volatile context to store the variable",
431                );
432                if let Some(var) = stack.last() {
433                    if var.context_index != context_index {
434                        stack.push(VariableInContext {
435                            variable: var.variable.clone(),
436                            context_index,
437                        });
438                    }
439                } else {
440                    stack.push(VariableInContext {
441                        variable: Variable::default(),
442                        context_index,
443                    });
444                }
445            }
446        }
447
448        VariableRefMut::from(&mut stack.last_mut().unwrap().variable)
449    }
450
451    /// Panics if the set contains any variable with an invalid context index.
452    #[cfg(test)]
453    fn assert_normalized(&self) {
454        for context in self.all_variables.values() {
455            for vars in context.windows(2) {
456                assert!(
457                    vars[0].context_index < vars[1].context_index,
458                    "invalid context index: {vars:?}",
459                );
460            }
461            if let Some(last) = context.last() {
462                assert!(
463                    last.context_index < self.contexts.len(),
464                    "invalid context index: {last:?}",
465                );
466            }
467        }
468    }
469
470    /// Gets the value of the specified scalar variable.
471    ///
472    /// This is a convenience function that retrieves the value of the specified
473    /// scalar variable. If the variable is unset or an array, this method
474    /// returns `None`.
475    ///
476    /// Note that this function does not apply any [`Quirk`] the variable may
477    /// have. Use [`Variable::expand`] to apply quirks.
478    #[must_use]
479    pub fn get_scalar<N>(&self, name: &N) -> Option<&str>
480    where
481        String: Borrow<N>,
482        N: Hash + Eq + ?Sized,
483    {
484        fn inner(var: &Variable) -> Option<&str> {
485            match var.value.as_ref()? {
486                Scalar(value) => Some(value),
487                Array(_) => None,
488            }
489        }
490        inner(self.get(name)?)
491    }
492
493    /// Unsets a variable.
494    ///
495    /// If successful, the return value is the previous value. If the specified
496    /// variable is read-only, this function fails with [`UnsetError`].
497    ///
498    /// The behavior of unsetting depends on the `scope`:
499    ///
500    /// - If the scope is `Global`, this function removes the variable from all
501    ///   contexts.
502    /// - If the scope is `Local`, this function removes the variable from the
503    ///   topmost [regular] context and any [volatile] context above it.
504    /// - If the scope is `Volatile`, this function removes the variable from
505    ///   any [volatile] context above the topmost [regular] context.
506    ///
507    /// In any case, this function may remove a variable from more than one
508    /// context, in which case the return value is the value in the topmost
509    /// context. If any of the removed variables is read-only, this function
510    /// fails with [`UnsetError`] and does not remove any variable.
511    ///
512    /// You cannot modify positional parameters using this function.
513    /// See [`positional_params_mut`](Self::positional_params_mut).
514    ///
515    /// [regular]: Context::Regular
516    /// [volatile]: Context::Volatile
517    pub fn unset<'a>(
518        &'a mut self,
519        name: &'a str,
520        scope: Scope,
521    ) -> Result<Option<Variable>, UnsetError<'a>> {
522        let Some(stack) = self.all_variables.get_mut(name) else {
523            return Ok(None);
524        };
525
526        // From which context should we unset?
527        let index = Self::index_of_context(scope, &self.contexts);
528
529        // Return an error if the variable is read-only.
530        // Unfortunately, this code fragment does not compile because the
531        // current Rust borrow checker is not smart enough.
532        // TODO Uncomment this code when the borrow checker is improved
533        // if let Some(read_only_location) = stack[index..]
534        //     .iter()
535        //     .filter_map(|vic| vic.variable.read_only_location.as_ref())
536        //     .next_back()
537        // {
538        //     return Err(UnsetError {
539        //         name,
540        //         read_only_location,
541        //     });
542        // }
543        if let Some(read_only_position) = stack[index..]
544            .iter()
545            .rposition(|vic| vic.variable.is_read_only())
546        {
547            let read_only_index = index + read_only_position;
548            let read_only_location = &stack[read_only_index].variable.read_only_location;
549            return Err(UnsetError {
550                name,
551                read_only_location: read_only_location.as_ref().unwrap(),
552            });
553        }
554
555        Ok(stack.drain(index..).next_back().map(|vic| vic.variable))
556    }
557
558    /// Returns an iterator of variables.
559    ///
560    /// The `scope` parameter chooses variables returned by the iterator:
561    ///
562    /// - `Global`: all variables
563    /// - `Local`: variables in the topmost [regular] context or above.
564    /// - `Volatile`: variables above the topmost [regular] context
565    ///
566    /// In all cases, the iterator ignores variables hidden by another.
567    ///
568    /// The order of iterated variables is unspecified.
569    ///
570    /// [regular]: Context::Regular
571    pub fn iter(&self, scope: Scope) -> Iter<'_> {
572        Iter {
573            inner: self.all_variables.iter(),
574            min_context_index: Self::index_of_context(scope, &self.contexts),
575        }
576    }
577
578    /// Returns environment variables in a new vector of C strings.
579    ///
580    /// This function returns a vector of C strings that represent the currently
581    /// defined environment variables. The strings are of the form `name=value`.
582    /// For variables that have an array value, the array items are concatenated
583    /// with a `:` between them to represent the value in a single string.
584    ///
585    /// This function ignores variables that have a name that contains a `=`,
586    /// since the `=` would be misinterpreted as the name-value separator.
587    ///
588    /// Currently, this function also ignores the variable if the name or value
589    /// contains a nul character, but this behavior may be changed in the
590    /// future.
591    #[must_use]
592    pub fn env_c_strings(&self) -> Vec<CString> {
593        self.all_variables
594            .iter()
595            .filter_map(|(name, vars)| {
596                let var = &vars.last()?.variable;
597                if !var.is_exported || name.contains('=') {
598                    return None;
599                }
600                let value = var.value.as_ref()?;
601                let mut result = name.clone();
602                result.push('=');
603                match value {
604                    Scalar(value) => result.push_str(value),
605                    Array(values) => write!(result, "{}", values.iter().format(":")).ok()?,
606                }
607                // TODO return something rather than dropping null-containing strings
608                CString::new(result).ok()
609            })
610            .collect()
611    }
612
613    /// Imports environment variables from an iterator.
614    ///
615    /// The argument iterator must yield name-value pairs. This function assigns
616    /// the values to the variable set, overwriting existing variables. The
617    /// variables are exported.
618    ///
619    /// If an assignment fails because of an existing read-only variable, this
620    /// function ignores the error and continues to the next assignment.
621    pub fn extend_env<I, K, V>(&mut self, vars: I)
622    where
623        I: IntoIterator<Item = (K, V)>,
624        K: Into<String>,
625        V: Into<String>,
626    {
627        for (name, value) in vars {
628            let mut var = self.get_or_new(name, Scope::Global);
629            if var.assign(value.into(), None).is_ok() {
630                var.export(true)
631            }
632        }
633    }
634
635    /// Initializes default variables.
636    ///
637    /// This function assigns the following variables to `self`:
638    ///
639    /// - `IFS=' \t\n'`
640    /// - `OPTIND=1`
641    /// - `PS1='$ '`
642    /// - `PS2='> '`
643    /// - `PS4='+ '`
644    /// - `LINENO` (with no value, but has its `quirk` set to [`Quirk::LineNumber`])
645    ///
646    /// The following variables are not assigned by this function as their
647    /// values cannot be determined independently:
648    ///
649    /// - `PPID`
650    /// - `PWD`
651    ///
652    /// This function ignores any assignment errors.
653    pub fn init(&mut self) {
654        const VARIABLES: &[(&str, &str)] = &[
655            (IFS, IFS_INITIAL_VALUE),
656            (OPTIND, OPTIND_INITIAL_VALUE),
657            (PS1, PS1_INITIAL_VALUE_NON_ROOT),
658            (PS2, PS2_INITIAL_VALUE),
659            (PS4, PS4_INITIAL_VALUE),
660        ];
661        for &(name, value) in VARIABLES {
662            self.get_or_new(name, Scope::Global)
663                .assign(value, None)
664                .ok();
665        }
666
667        self.get_or_new(LINENO, Scope::Global)
668            .set_quirk(Some(Quirk::LineNumber))
669    }
670
671    /// Returns a reference to the positional parameters.
672    ///
673    /// Every regular context starts with an empty array of positional
674    /// parameters, and volatile contexts cannot have positional parameters.
675    /// This function returns a reference to the positional parameters of the
676    /// topmost regular context.
677    ///
678    /// See also [`positional_params_mut`](Self::positional_params_mut).
679    #[must_use]
680    pub fn positional_params(&self) -> &PositionalParams {
681        self.contexts
682            .iter()
683            .rev()
684            .find_map(|context| match context {
685                Context::Regular { positional_params } => Some(positional_params),
686                Context::Volatile => None,
687            })
688            .expect("base context has gone")
689    }
690
691    /// Returns a mutable reference to the positional parameters.
692    ///
693    /// Every regular context starts with an empty array of positional
694    /// parameters, and volatile contexts cannot have positional parameters.
695    /// This function returns a reference to the positional parameters of the
696    /// topmost regular context.
697    #[must_use]
698    pub fn positional_params_mut(&mut self) -> &mut PositionalParams {
699        self.contexts
700            .iter_mut()
701            .rev()
702            .find_map(|context| match context {
703                Context::Regular { positional_params } => Some(positional_params),
704                Context::Volatile => None,
705            })
706            .expect("base context has gone")
707    }
708
709    fn push_context_impl(&mut self, context: Context) {
710        self.contexts.push(context);
711    }
712
713    fn pop_context_impl(&mut self) {
714        debug_assert!(!self.contexts.is_empty());
715        assert_ne!(self.contexts.len(), 1, "cannot pop the base context");
716        self.contexts.pop();
717        self.all_variables.retain(|_, stack| {
718            stack.pop_if(|vic| {
719                // Remove the variable if it is in a context that has been popped.
720                // The popped context is always the topmost context.
721                vic.context_index >= self.contexts.len()
722            });
723            !stack.is_empty()
724        })
725    }
726}
727
728impl<'a> Iterator for Iter<'a> {
729    type Item = (&'a str, &'a Variable);
730
731    fn next(&mut self) -> Option<(&'a str, &'a Variable)> {
732        loop {
733            let next = self.inner.next()?;
734            if let Some(variable) = next.1.last() {
735                if variable.context_index >= self.min_context_index {
736                    return Some((next.0, &variable.variable));
737                }
738            }
739        }
740    }
741
742    fn size_hint(&self) -> (usize, Option<usize>) {
743        let (_min, max) = self.inner.size_hint();
744        (0, max)
745    }
746}
747
748impl FusedIterator for Iter<'_> {}
749
750mod guard;
751
752pub use self::guard::{ContextGuard, EnvContextGuard};
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757
758    #[test]
759    fn new_variable_in_global_scope() {
760        let mut set = VariableSet::new();
761        set.push_context_impl(Context::default());
762        set.push_context_impl(Context::Volatile);
763
764        let mut var = set.get_or_new("foo", Scope::Global);
765
766        assert_eq!(*var, Variable::default());
767        var.assign("VALUE", None).unwrap();
768        set.assert_normalized();
769        set.pop_context_impl();
770        set.pop_context_impl();
771        // The global variable still exists.
772        assert_eq!(set.get("foo").unwrap().value, Some("VALUE".into()));
773    }
774
775    #[test]
776    fn existing_variable_in_global_scope() {
777        let mut set = VariableSet::new();
778        let mut var = set.get_or_new("foo", Scope::Global);
779        var.assign("ONE", None).unwrap();
780        set.push_context_impl(Context::default());
781        set.push_context_impl(Context::Volatile);
782
783        let mut var = set.get_or_new("foo", Scope::Global);
784
785        assert_eq!(var.value, Some("ONE".into()));
786        var.assign("TWO", Location::dummy("somewhere")).unwrap();
787        set.assert_normalized();
788        set.pop_context_impl();
789        set.pop_context_impl();
790        // The updated global variable still exists.
791        let var = set.get("foo").unwrap();
792        assert_eq!(var.value, Some("TWO".into()));
793        assert_eq!(
794            var.last_assigned_location,
795            Some(Location::dummy("somewhere")),
796        );
797    }
798
799    #[test]
800    fn new_variable_in_local_scope() {
801        // This test case creates two local variables in separate contexts.
802        let mut set = VariableSet::new();
803        set.push_context_impl(Context::default());
804
805        let mut var = set.get_or_new("foo", Scope::Local);
806
807        assert_eq!(*var, Variable::default());
808
809        var.assign("OUTER", None).unwrap();
810        set.push_context_impl(Context::default());
811        set.push_context_impl(Context::Volatile);
812
813        let mut var = set.get_or_new("foo", Scope::Local);
814
815        assert_eq!(*var, Variable::default());
816        var.assign("INNER", Location::dummy("location")).unwrap();
817        set.assert_normalized();
818        set.pop_context_impl(); // volatile
819        assert_eq!(set.get("foo").unwrap().value, Some("INNER".into()));
820        set.pop_context_impl(); // regular
821        assert_eq!(set.get("foo").unwrap().value, Some("OUTER".into()));
822        set.pop_context_impl(); // regular
823        assert_eq!(set.get("foo"), None);
824    }
825
826    #[test]
827    fn existing_variable_in_local_scope() {
828        let mut set = VariableSet::new();
829        set.push_context_impl(Context::default());
830        let mut var = set.get_or_new("foo", Scope::Local);
831        var.assign("OLD", None).unwrap();
832
833        let mut var = set.get_or_new("foo", Scope::Local);
834
835        assert_eq!(var.value, Some("OLD".into()));
836        var.assign("NEW", None).unwrap();
837        assert_eq!(set.get("foo").unwrap().value, Some("NEW".into()));
838        set.assert_normalized();
839        set.pop_context_impl();
840        assert_eq!(set.get("foo"), None);
841    }
842
843    #[test]
844    fn new_variable_in_volatile_scope() {
845        let mut set = VariableSet::new();
846        set.push_context_impl(Context::Volatile);
847        set.push_context_impl(Context::Volatile);
848
849        let mut var = set.get_or_new("foo", Scope::Volatile);
850
851        assert_eq!(*var, Variable::default());
852        var.assign("VOLATILE", None).unwrap();
853        assert_eq!(set.get("foo").unwrap().value, Some("VOLATILE".into()));
854        set.assert_normalized();
855        set.pop_context_impl();
856        assert_eq!(set.get("foo"), None);
857    }
858
859    #[test]
860    fn cloning_existing_regular_variable_to_volatile_context() {
861        let mut set = VariableSet::new();
862        let mut var = set.get_or_new("foo", Scope::Global);
863        var.assign("VALUE", None).unwrap();
864        var.make_read_only(Location::dummy("read-only location"));
865        let save_var = var.clone();
866        set.push_context_impl(Context::Volatile);
867        set.push_context_impl(Context::Volatile);
868
869        let mut var = set.get_or_new("foo", Scope::Volatile);
870
871        assert_eq!(*var, save_var);
872        var.export(true);
873        assert!(set.get("foo").unwrap().is_exported);
874        set.assert_normalized();
875        set.pop_context_impl();
876        // The exported variable is a volatile clone of the global variable.
877        // The global variable is still not exported.
878        assert_eq!(set.get("foo"), Some(&save_var));
879    }
880
881    #[test]
882    fn existing_variable_in_volatile_scope() {
883        let mut set = VariableSet::new();
884        set.push_context_impl(Context::Volatile);
885        let mut var = set.get_or_new("foo", Scope::Volatile);
886        var.assign("INITIAL", None).unwrap();
887
888        let mut var = set.get_or_new("foo", Scope::Volatile);
889
890        assert_eq!(var.value, Some("INITIAL".into()));
891        var.assign(Value::array(["MODIFIED"]), Location::dummy("somewhere"))
892            .unwrap();
893        assert_eq!(
894            set.get("foo").unwrap().value,
895            Some(Value::array(["MODIFIED"])),
896        );
897        set.assert_normalized();
898        set.pop_context_impl();
899        assert_eq!(set.get("foo"), None);
900    }
901
902    #[test]
903    fn lowering_volatile_variable_to_base_context() {
904        let mut set = VariableSet::new();
905        set.push_context_impl(Context::default());
906        set.push_context_impl(Context::Volatile);
907        let mut var = set.get_or_new("foo", Scope::Volatile);
908        var.assign("DUMMY", None).unwrap();
909        set.push_context_impl(Context::Volatile);
910        let mut var = set.get_or_new("foo", Scope::Volatile);
911        var.assign("VOLATILE", Location::dummy("anywhere")).unwrap();
912        var.export(true);
913
914        let mut var = set.get_or_new("foo", Scope::Global);
915
916        assert_eq!(var.value, Some("VOLATILE".into()));
917        assert_eq!(
918            var.last_assigned_location,
919            Some(Location::dummy("anywhere")),
920        );
921        var.assign("NEW", Location::dummy("somewhere")).unwrap();
922        set.assert_normalized();
923        set.pop_context_impl();
924        set.pop_context_impl();
925        set.pop_context_impl();
926        // The value DUMMY is now gone.
927        // The value VOLATILE has been overwritten by NEW.
928        let var = set.get("foo").unwrap();
929        assert_eq!(var.value, Some("NEW".into()));
930        assert_eq!(
931            var.last_assigned_location,
932            Some(Location::dummy("somewhere")),
933        );
934        // But it's still exported.
935        assert!(var.is_exported);
936    }
937
938    #[test]
939    fn lowering_volatile_variable_to_middle_regular_context() {
940        let mut set = VariableSet::new();
941        let mut var = set.get_or_new("foo", Scope::Local);
942        var.assign("ONE", None).unwrap();
943        set.push_context_impl(Context::default());
944        let mut var = set.get_or_new("foo", Scope::Local);
945        var.assign("TWO", None).unwrap();
946        set.push_context_impl(Context::default());
947        set.push_context_impl(Context::Volatile);
948        let mut var = set.get_or_new("foo", Scope::Volatile);
949        var.assign("VOLATILE", Location::dummy("anywhere")).unwrap();
950        var.export(true);
951
952        let mut var = set.get_or_new("foo", Scope::Global);
953
954        assert_eq!(var.value, Some("VOLATILE".into()));
955        assert_eq!(
956            var.last_assigned_location,
957            Some(Location::dummy("anywhere")),
958        );
959        var.assign("NEW", Location::dummy("somewhere")).unwrap();
960        set.assert_normalized();
961        set.pop_context_impl();
962        set.pop_context_impl();
963        // The value TWO has been overwritten by NEW.
964        let var = set.get("foo").unwrap();
965        assert_eq!(var.value, Some("NEW".into()));
966        assert_eq!(
967            var.last_assigned_location,
968            Some(Location::dummy("somewhere")),
969        );
970        // But it's still exported.
971        assert!(var.is_exported);
972        set.pop_context_impl();
973        // The value ONE is still there.
974        let var = set.get("foo").unwrap();
975        assert_eq!(var.value, Some("ONE".into()));
976    }
977
978    #[test]
979    fn lowering_volatile_variable_to_topmost_regular_context_without_existing_variable() {
980        let mut set = VariableSet::new();
981        set.push_context_impl(Context::default());
982        set.push_context_impl(Context::default());
983        set.push_context_impl(Context::Volatile);
984        let mut var = set.get_or_new("foo", Scope::Volatile);
985        var.assign("DUMMY", None).unwrap();
986        set.push_context_impl(Context::Volatile);
987        let mut var = set.get_or_new("foo", Scope::Volatile);
988        var.assign("VOLATILE", Location::dummy("anywhere")).unwrap();
989        var.export(true);
990
991        let mut var = set.get_or_new("foo", Scope::Local);
992
993        assert_eq!(var.value, Some("VOLATILE".into()));
994        assert_eq!(
995            var.last_assigned_location,
996            Some(Location::dummy("anywhere")),
997        );
998        var.assign("NEW", Location::dummy("somewhere")).unwrap();
999        set.assert_normalized();
1000        set.pop_context_impl();
1001        set.pop_context_impl();
1002        // The value DUMMY is now gone.
1003        // The value VOLATILE has been overwritten by NEW.
1004        let var = set.get("foo").unwrap();
1005        assert_eq!(var.value, Some("NEW".into()));
1006        assert_eq!(
1007            var.last_assigned_location,
1008            Some(Location::dummy("somewhere")),
1009        );
1010        // But it's still exported.
1011        assert!(var.is_exported);
1012    }
1013
1014    #[test]
1015    fn lowering_volatile_variable_to_topmost_regular_context_overwriting_existing_variable() {
1016        let mut set = VariableSet::new();
1017        set.push_context_impl(Context::default());
1018        set.push_context_impl(Context::default());
1019        let mut var = set.get_or_new("foo", Scope::Local);
1020        var.assign("OLD", None).unwrap();
1021        set.push_context_impl(Context::Volatile);
1022        let mut var = set.get_or_new("foo", Scope::Volatile);
1023        var.assign("DUMMY", None).unwrap();
1024        set.push_context_impl(Context::Volatile);
1025        let mut var = set.get_or_new("foo", Scope::Volatile);
1026        var.assign("VOLATILE", Location::dummy("first")).unwrap();
1027        var.export(true);
1028        set.push_context_impl(Context::Volatile);
1029
1030        let mut var = set.get_or_new("foo", Scope::Local);
1031
1032        assert_eq!(var.value, Some("VOLATILE".into()));
1033        assert_eq!(var.last_assigned_location, Some(Location::dummy("first")));
1034        var.assign("NEW", Location::dummy("second")).unwrap();
1035        set.assert_normalized();
1036        set.pop_context_impl();
1037        set.pop_context_impl();
1038        set.pop_context_impl();
1039        // The value DUMMY is now gone.
1040        // The value OLD has been overwritten by NEW.
1041        let var = set.get("foo").unwrap();
1042        assert_eq!(var.value, Some("NEW".into()));
1043        assert_eq!(var.last_assigned_location, Some(Location::dummy("second")));
1044        // But it's still exported.
1045        assert!(var.is_exported);
1046    }
1047
1048    #[test]
1049    #[should_panic(expected = "no volatile context to store the variable")]
1050    fn missing_volatile_context() {
1051        let mut set = VariableSet::new();
1052        set.get_or_new("foo", Scope::Volatile);
1053    }
1054
1055    #[test]
1056    fn getting_variables_with_scopes() {
1057        let mut set = VariableSet::new();
1058        set.get_or_new("global", Scope::Global)
1059            .assign("G", None)
1060            .unwrap();
1061        set.push_context_impl(Context::default());
1062        set.get_or_new("local", Scope::Local)
1063            .assign("L", None)
1064            .unwrap();
1065        set.push_context_impl(Context::Volatile);
1066        set.get_or_new("volatile", Scope::Volatile)
1067            .assign("V", None)
1068            .unwrap();
1069
1070        assert_eq!(
1071            set.get_scoped("global", Scope::Global),
1072            Some(&Variable::new("G")),
1073        );
1074        assert_eq!(set.get_scoped("global", Scope::Local), None);
1075        assert_eq!(set.get_scoped("global", Scope::Volatile), None);
1076
1077        assert_eq!(
1078            set.get_scoped("local", Scope::Global),
1079            Some(&Variable::new("L"))
1080        );
1081        assert_eq!(
1082            set.get_scoped("local", Scope::Local),
1083            Some(&Variable::new("L"))
1084        );
1085        assert_eq!(set.get_scoped("local", Scope::Volatile), None);
1086
1087        assert_eq!(
1088            set.get_scoped("volatile", Scope::Global),
1089            Some(&Variable::new("V"))
1090        );
1091        assert_eq!(
1092            set.get_scoped("volatile", Scope::Local),
1093            Some(&Variable::new("V"))
1094        );
1095        assert_eq!(
1096            set.get_scoped("volatile", Scope::Volatile),
1097            Some(&Variable::new("V"))
1098        );
1099    }
1100
1101    #[test]
1102    fn unsetting_nonexisting_variable() {
1103        let mut variables = VariableSet::new();
1104        let result = variables.unset("", Scope::Global).unwrap();
1105        assert_eq!(result, None);
1106    }
1107
1108    #[test]
1109    fn unsetting_variable_with_one_context() {
1110        let mut variables = VariableSet::new();
1111        variables
1112            .get_or_new("foo", Scope::Global)
1113            .assign("X", None)
1114            .unwrap();
1115
1116        let result = variables.unset("foo", Scope::Global).unwrap();
1117        assert_eq!(result, Some(Variable::new("X")));
1118        assert_eq!(variables.get("foo"), None);
1119    }
1120
1121    #[test]
1122    fn unsetting_variables_from_all_contexts() {
1123        let mut variables = VariableSet::new();
1124        variables
1125            .get_or_new("foo", Scope::Global)
1126            .assign("X", None)
1127            .unwrap();
1128        variables.push_context_impl(Context::default());
1129        variables
1130            .get_or_new("foo", Scope::Local)
1131            .assign("Y", None)
1132            .unwrap();
1133        variables.push_context_impl(Context::Volatile);
1134        variables
1135            .get_or_new("foo", Scope::Volatile)
1136            .assign("Z", None)
1137            .unwrap();
1138
1139        let result = variables.unset("foo", Scope::Global).unwrap();
1140        assert_eq!(result, Some(Variable::new("Z")));
1141        assert_eq!(variables.get("foo"), None);
1142    }
1143
1144    #[test]
1145    fn unsetting_variable_from_local_context() {
1146        let mut variables = VariableSet::new();
1147        variables
1148            .get_or_new("foo", Scope::Global)
1149            .assign("A", None)
1150            .unwrap();
1151        variables.push_context_impl(Context::default());
1152        // Non-local read-only variable does not prevent unsetting
1153        let mut readonly_foo = variables.get_or_new("foo", Scope::Local);
1154        readonly_foo.assign("B", None).unwrap();
1155        readonly_foo.make_read_only(Location::dummy("dummy"));
1156        let readonly_foo = readonly_foo.clone();
1157        variables.push_context_impl(Context::default());
1158        variables
1159            .get_or_new("foo", Scope::Local)
1160            .assign("C", None)
1161            .unwrap();
1162        variables.push_context_impl(Context::Volatile);
1163        variables
1164            .get_or_new("foo", Scope::Volatile)
1165            .assign("D", None)
1166            .unwrap();
1167
1168        let result = variables.unset("foo", Scope::Local).unwrap();
1169        assert_eq!(result, Some(Variable::new("D")));
1170        assert_eq!(variables.get("foo"), Some(&readonly_foo));
1171    }
1172
1173    #[test]
1174    fn unsetting_nonexisting_variable_in_local_context() {
1175        let mut variables = VariableSet::new();
1176        variables
1177            .get_or_new("foo", Scope::Global)
1178            .assign("A", None)
1179            .unwrap();
1180        variables.push_context_impl(Context::default());
1181
1182        let result = variables.unset("foo", Scope::Local).unwrap();
1183        assert_eq!(result, None);
1184        assert_eq!(variables.get("foo"), Some(&Variable::new("A")));
1185    }
1186
1187    #[test]
1188    fn unsetting_variable_from_volatile_context() {
1189        let mut variables = VariableSet::new();
1190        variables
1191            .get_or_new("foo", Scope::Global)
1192            .assign("A", None)
1193            .unwrap();
1194        variables.push_context_impl(Context::default());
1195        variables
1196            .get_or_new("foo", Scope::Local)
1197            .assign("B", None)
1198            .unwrap();
1199        variables.push_context_impl(Context::Volatile);
1200        variables
1201            .get_or_new("foo", Scope::Volatile)
1202            .assign("C", None)
1203            .unwrap();
1204        variables.push_context_impl(Context::Volatile);
1205        variables
1206            .get_or_new("foo", Scope::Volatile)
1207            .assign("D", None)
1208            .unwrap();
1209
1210        let result = variables.unset("foo", Scope::Volatile).unwrap();
1211        assert_eq!(result, Some(Variable::new("D")));
1212        assert_eq!(variables.get("foo"), Some(&Variable::new("B")));
1213    }
1214
1215    #[test]
1216    fn unsetting_nonexisting_variable_in_volatile_context() {
1217        let mut variables = VariableSet::new();
1218        variables
1219            .get_or_new("foo", Scope::Global)
1220            .assign("A", None)
1221            .unwrap();
1222        variables.push_context_impl(Context::Volatile);
1223
1224        let result = variables.unset("foo", Scope::Volatile).unwrap();
1225        assert_eq!(result, None);
1226        assert_eq!(variables.get("foo"), Some(&Variable::new("A")));
1227    }
1228
1229    #[test]
1230    fn unsetting_readonly_variable() {
1231        let read_only_location = &Location::dummy("read-only");
1232        let mut variables = VariableSet::new();
1233        let mut foo = variables.get_or_new("foo", Scope::Global);
1234        foo.assign("A", None).unwrap();
1235        variables.push_context_impl(Context::default());
1236        let mut foo = variables.get_or_new("foo", Scope::Local);
1237        foo.assign("B", None).unwrap();
1238        foo.make_read_only(Location::dummy("dummy"));
1239        variables.push_context_impl(Context::default());
1240        let mut foo = variables.get_or_new("foo", Scope::Local);
1241        foo.assign("C", None).unwrap();
1242        foo.make_read_only(read_only_location.clone());
1243        variables.push_context_impl(Context::default());
1244        let mut foo = variables.get_or_new("foo", Scope::Local);
1245        foo.assign("D", None).unwrap();
1246
1247        let error = variables.unset("foo", Scope::Global).unwrap_err();
1248        assert_eq!(
1249            error,
1250            UnsetError {
1251                name: "foo",
1252                read_only_location
1253            }
1254        );
1255        assert_eq!(variables.get("foo"), Some(&Variable::new("D")));
1256    }
1257
1258    #[test]
1259    #[should_panic(expected = "cannot pop the base context")]
1260    fn cannot_pop_base_context() {
1261        let mut variables = VariableSet::new();
1262        variables.pop_context_impl();
1263    }
1264
1265    fn test_iter<F: FnOnce(&VariableSet)>(f: F) {
1266        let mut set = VariableSet::new();
1267
1268        let mut var = set.get_or_new("global", Scope::Global);
1269        var.assign("global value", None).unwrap();
1270        var.export(true);
1271        let mut var = set.get_or_new("local", Scope::Global);
1272        var.assign("hidden value", None).unwrap();
1273
1274        let mut set = set.push_context(Context::default());
1275
1276        let mut var = set.get_or_new("local", Scope::Local);
1277        var.assign("visible value", None).unwrap();
1278        let mut var = set.get_or_new("volatile", Scope::Local);
1279        var.assign("hidden value", None).unwrap();
1280
1281        let mut set = set.push_context(Context::Volatile);
1282
1283        let mut var = set.get_or_new("volatile", Scope::Volatile);
1284        var.assign("volatile value", None).unwrap();
1285
1286        f(&mut set);
1287    }
1288
1289    #[test]
1290    fn iter_global() {
1291        test_iter(|set| {
1292            let mut v: Vec<_> = set.iter(Scope::Global).collect();
1293            v.sort_unstable_by_key(|&(name, _)| name);
1294            assert_eq!(
1295                v,
1296                [
1297                    ("global", &Variable::new("global value").export()),
1298                    ("local", &Variable::new("visible value")),
1299                    ("volatile", &Variable::new("volatile value"))
1300                ]
1301            );
1302        })
1303    }
1304
1305    #[test]
1306    fn iter_local() {
1307        test_iter(|set| {
1308            let mut v: Vec<_> = set.iter(Scope::Local).collect();
1309            v.sort_unstable_by_key(|&(name, _)| name);
1310            assert_eq!(
1311                v,
1312                [
1313                    ("local", &Variable::new("visible value")),
1314                    ("volatile", &Variable::new("volatile value"))
1315                ]
1316            );
1317        })
1318    }
1319
1320    #[test]
1321    fn iter_volatile() {
1322        test_iter(|set| {
1323            let mut v: Vec<_> = set.iter(Scope::Volatile).collect();
1324            v.sort_unstable_by_key(|&(name, _)| name);
1325            assert_eq!(v, [("volatile", &Variable::new("volatile value"))]);
1326        })
1327    }
1328
1329    #[test]
1330    fn iter_size_hint() {
1331        test_iter(|set| {
1332            assert_eq!(set.iter(Scope::Global).size_hint(), (0, Some(3)));
1333            assert_eq!(set.iter(Scope::Local).size_hint(), (0, Some(3)));
1334            assert_eq!(set.iter(Scope::Volatile).size_hint(), (0, Some(3)));
1335        })
1336    }
1337
1338    #[test]
1339    fn env_c_strings() {
1340        let mut variables = VariableSet::new();
1341        assert_eq!(variables.env_c_strings(), [] as [CString; 0]);
1342
1343        let mut var = variables.get_or_new("foo", Scope::Global);
1344        var.assign("FOO", None).unwrap();
1345        var.export(true);
1346        let mut var = variables.get_or_new("bar", Scope::Global);
1347        var.assign(Value::array(["BAR"]), None).unwrap();
1348        var.export(true);
1349        let mut var = variables.get_or_new("baz", Scope::Global);
1350        var.assign(Value::array(["1", "two", "3"]), None).unwrap();
1351        var.export(true);
1352        let mut var = variables.get_or_new("null", Scope::Global);
1353        var.assign("not exported", None).unwrap();
1354        variables.get_or_new("none", Scope::Global);
1355
1356        let mut ss = variables.env_c_strings();
1357        ss.sort_unstable();
1358        assert_eq!(
1359            &ss,
1360            &[
1361                c"bar=BAR".to_owned(),
1362                c"baz=1:two:3".to_owned(),
1363                c"foo=FOO".to_owned()
1364            ]
1365        );
1366    }
1367
1368    #[test]
1369    fn env_c_strings_with_equal_in_name() {
1370        let mut variables = VariableSet::new();
1371        let mut var = variables.get_or_new("foo", Scope::Global);
1372        var.assign("FOO", None).unwrap();
1373        var.export(true);
1374        let mut var = variables.get_or_new("foo=bar", Scope::Global);
1375        var.assign("BAR", None).unwrap();
1376        var.export(true);
1377
1378        let mut ss = variables.env_c_strings();
1379        ss.sort_unstable();
1380        // The expected behavior is that the variable named "foo=bar" is
1381        // ignored since the name contains `=`, and only variable "foo" is
1382        // exported.
1383        assert_eq!(&ss, &[c"foo=FOO".to_owned(),]);
1384    }
1385
1386    #[test]
1387    fn extend_env() {
1388        let mut variables = VariableSet::new();
1389
1390        variables.extend_env([("foo", "FOO"), ("bar", "OK")]);
1391
1392        let foo = variables.get("foo").unwrap();
1393        assert_eq!(foo.value, Some("FOO".into()));
1394        assert!(foo.is_exported);
1395        let bar = variables.get("bar").unwrap();
1396        assert_eq!(bar.value, Some("OK".into()));
1397        assert!(bar.is_exported);
1398    }
1399
1400    #[test]
1401    fn init_lineno() {
1402        let mut variables = VariableSet::new();
1403        variables.init();
1404        let v = variables.get(LINENO).unwrap();
1405        assert_eq!(v.value, None);
1406        assert_eq!(v.quirk, Some(Quirk::LineNumber));
1407        assert_eq!(v.last_assigned_location, None);
1408        assert!(!v.is_exported);
1409        assert_eq!(v.read_only_location, None);
1410    }
1411
1412    #[test]
1413    fn positional_params_in_base_context() {
1414        let mut variables = VariableSet::new();
1415        assert_eq!(variables.positional_params().values, [] as [String; 0]);
1416
1417        let params = variables.positional_params_mut();
1418        params.values.push("foo".to_string());
1419        params.values.push("bar".to_string());
1420
1421        assert_eq!(
1422            variables.positional_params().values,
1423            ["foo".to_string(), "bar".to_string()],
1424        );
1425    }
1426
1427    #[test]
1428    fn positional_params_in_second_regular_context() {
1429        let mut variables = VariableSet::new();
1430        variables.push_context_impl(Context::default());
1431        assert_eq!(variables.positional_params().values, [] as [String; 0]);
1432
1433        let params = variables.positional_params_mut();
1434        params.values.push("1".to_string());
1435
1436        assert_eq!(variables.positional_params().values, ["1".to_string()]);
1437    }
1438
1439    #[test]
1440    fn getting_positional_params_in_volatile_context() {
1441        let mut variables = VariableSet::new();
1442
1443        let params = variables.positional_params_mut();
1444        params.values.push("a".to_string());
1445        params.values.push("b".to_string());
1446        params.values.push("c".to_string());
1447
1448        variables.push_context_impl(Context::Volatile);
1449        assert_eq!(
1450            variables.positional_params().values,
1451            ["a".to_string(), "b".to_string(), "c".to_string()],
1452        );
1453    }
1454
1455    #[test]
1456    fn setting_positional_params_in_volatile_context() {
1457        let mut variables = VariableSet::new();
1458        variables.push_context_impl(Context::Volatile);
1459
1460        let params = variables.positional_params_mut();
1461        params.values.push("x".to_string());
1462
1463        variables.pop_context_impl();
1464        assert_eq!(variables.positional_params().values, ["x".to_string()]);
1465    }
1466}